Группировать список внутри объекта по нескольким атрибутам: Java 8 - PullRequest
0 голосов
/ 30 января 2019

Как сгруппировать список внутри объекта по двум атрибутам объекта?

Я использую Drools 7.9.0 и Java 8. У меня есть класс Result, который используется в качестве возврата ккаждое соответствующее правило Drools.

import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
public class Result implements Serializable {

    private Integer id;
    private String name;
    private List<Occurrences> occurrences = new ArrayList<>();

    public void addOccurrence(Occurrences occurrence) {
        this.occurrences.add(occurrence);
    }
}

После выполнения Drools я получаю List<Result>.Преобразование в JSON выглядит следующим образом.

ТЕКУЩИЙ РЕЗУЛЬТАТ:

[
     {
        name: "Whatever"
        id: 0001,
        occurrences: [{
           (...)
        }]
    },
    {
        name: "Whatever"
        id: 0001,
        occurrences: [{
           (...)
        }]
    },
    {
        name: "Whatever"
        id: 0002,
        occurrences: [{
           (...)
        }]
    },
    {
        name: "Other thing"
        id: 0002,
        occurrences: [{
           (...)
        }]
    }
]

Мне нужно сгруппировать мой список Result с, чтобы ocurrences былисгруппированы по id и name, вот так.

ОЖИДАЕМЫЙ РЕЗУЛЬТАТ:

[
    {
        name: "Whatever"
        id: 0001,
        occurrences: [{
           (...)
        }, {
           (...)
        }]
    },
    {
        name: "Whatever"
        id: 0002,
        occurrences: [{
           (...)
        }]
    },
    {
        name: "Other thing"
        id: 0002,
        occurrences: [{
           (...)
        }]
    }
]

Каков наилучший способ реализовать это?У меня есть два варианта:

  1. Изменить правила Drools, так что мой List<Result> уже структурирован так, как мне нужно.Может быть, создать класс с addResult методом, который проверяет список на id и name и добавить вхождение в нужную запись.Но это не идеально, потому что это увеличит сложность внутри правил.
  2. Постобработка my List<Result>, поэтому он группирует occurrences по id и name.Но я понятия не имею, как сделать это оптимизированным и простым способом.

Какой лучший способ сделать второй вариант?

Ответы [ 4 ]

0 голосов
/ 30 января 2019

Я бы добавил следующий метод к Result:

Result addOccurrences (List<Occurrences> occurrences) {
  this.occurrences.addAll (occurrences);
  return this;
}

И тогда вы можете использовать потоки:

List<Result> collect = results.stream ()
  .collect (Collectors.groupingBy (Result::getId, Collectors.groupingBy (Result::getName, Collectors.toList ())))
  .values ()
  .stream ()
  .map (Map::values)
  .flatMap (Collection::stream)
  .map (Collection::stream)
  .map (resultStream -> resultStream.reduce ((r0, r1) -> r0.addOccurrences (r1.getOccurrences ())))
  .filter (Optional::isPresent)
  .map (Optional::get)
  .collect (Collectors.toList ());
0 голосов
/ 30 января 2019

Простой способ 2-го варианта может быть следующим:

Map<String, List<Occurences>> resultsMapped = new HashMap<>();
List<Result> collect = results.forEach(r -> resultsMapped.merge(String.format("%s%d", r.name, r.id), 
            r.occurrences, (currentOccurences, newOccurences) -> {
               currentOccurences.addAll(newOccurences);
               return currentOccurences;
            });

Который хранит на карте ваши вхождения, ключом является имя и идентификатор, объединенные в строку.Этот ключ не оптимизирован.

0 голосов
/ 30 января 2019

Вы также можете использовать несколько потоков для этого.Это сгруппирует по ID и имени, а затем создаст новый объект Result для каждой группы, содержащий все вхождения группы.

Collection<Result> processed = results.stream().collect(Collectors.groupingBy(r -> r.getId() + r.getName(), 
            Collectors.collectingAndThen(Collectors.toList(),
            l -> new Result(l.get(0).getId(), l.get(0).getName(), 
                    l.stream().flatMap(c -> c.getOccurrences().stream()).collect(Collectors.toList()))
    ))).values();

Для этого требуется добавить AllArgsConstructor в класс Result, а такжееще несколько получателей, но вы можете настроить это, как вам нравится.Ключ, используемый для группировки, в этом примере небезопасен.

0 голосов
/ 30 января 2019

Вы можете сделать это так,

Map<String, List<Result>> resultsWithSameIdAndName = results.stream()
    .collect(Collectors.groupingBy(r -> r.getId() + ":" + r.getName()));

List<Result> mergedResults = resultsWithSameIdAndName.entrySet().stream()
    .map(e -> new Result(Integer.valueOf(e.getKey().split(":")[0]), 
        e.getKey().split(":")[1],
        e.getValue().stream().flatMap(r -> r.getOccurrences().stream())
            .collect(Collectors.toList())))
        .collect(Collectors.toList());
...