2015-04-09 1 views
2

У меня есть структура как это (упрощенный):Как сгладить этот список <C1> где C1 содержит коллекцию C2 одного элемента с использованием Stream API?

class C1 { 
    Integer id; 
    Set<C2> c2Set; 
    public C1 add(C2 c2) { 
     c2Set.add(c2); 
     return this; 
    } 
    //equals and hashcode methods based on id field. 
    @Override public String toString() { 
     return "C1: {" + id + ", " + c2Set + "}"; 
    } 
} 
class C2 { 
    Integer id; 
    //equals and hashcode methods based on id field. 
    @Override 
    public String toString() { 
     return "C2: " + id; 
    } 
} 

Чтобы заполнить эту структуру, я выполнить следующий запрос:

SELECT c1.id, c2.id 
FROM tablex c1 
    INNER JOIN tablex_tabley d ON d.c1id = c1.id 
    INNER JOIN tabley c2 ON c2.id = d.c2id 
ORDER BY c1.id; 

И это считывается непосредственно из (используя JDBI) линии ResultSet по линии. Структура чтения данных выглядит примерно так:

List<C1> c1List = asList(
    new C1(1).add(new C2(1)), 
    new C1(1).add(new C2(2)), 
    new C1(2).add(new C2(1)) 
); 

Мне нужно, чтобы сгладить этот список, чтобы он выглядел так, как будто она была создана, как это:

List<C1> c1List = asList(
    new C1(1).add(new C2(1)).add(new C2(2)), 
    new C1(2).add(new C2(1)) 
); 

В настоящее время я использую этот кусок кода что делает работу:

Map<C1, C1> c1Map = new LinkedHashMap<>(); 
for (C1 c1 : c1List) { 
    if (!c1Map.containsKey(c1)) { 
     c1Map.put(c1, c1); 
    } else { 
     C1 prevC1 = c1Map.get(c1); 
     for (C2 c2 : c1.c2Set) { 
      prevC1.add(c2); 
     } 
    } 
} 
List<C1> c1ListReduce = new ArrayList<>(c1Map.values()); 
//just to check the results 
System.out.println(c1ListReduce); 

Как я могу достичь того же, используя потоки Java 8? Я не могу найти способ сгруппировать все экземпляры C2, принадлежащие экземпляру C1, и вернуть List<C1> (или любую другую коллекцию, возможно, Set<C1>), где они сглажены, не зная, какие элементы я уже посетил, и их добавление для их добавления.

ответ

3

В том же духе Дмитрий Гинзбург, но реализован по-разному.

List<C1> list = c1List.stream() 
         .collect(toMap(c -> c.id, c -> c.c2Set, (set1, set2) -> Stream.concat(set1.stream(), set2.stream()).collect(toSet()))) 
         .entrySet() 
         .stream() 
         .map(e -> new C1(e.getKey(), e.getValue())) 
         .collect(toList()); 

Он использует сборщик toMap(). С List<C1> вы создаете Map<Integer, Set<C2>>, который сопоставляет каждый идентификатор C1 с его набором c2Set. Если у вас есть один подобный идентификатор, вы объедините два значения (например, наборы) в другой новый набор.

Наконец, вы получаете набор записей и сопоставляете каждую запись с новым экземпляром C1 и собираете все экземпляры в список.

(Я предположил, что есть конструктор, который принимает параметр Set as)

+0

Единственная часть, которую я не понимаю, это: '(set1, set2) -> Stream.concat (set1.stream(), set2.stream())'. Что это значит? –

+1

@LuiggiMendoza Это функция слияния, используемая, если у вас есть два одинаковых ключа. Эта функция принимает два значения в качестве параметра (что соответствует значению, связанному с ключом) и возвращает значение того же типа. Здесь каждое значение представляет собой набор, связанный с конкретным экземпляром C1. Поэтому 'set1' соответствует набору' c2Set' одного экземпляра C1 и set2 в набор 'c2Set' другого экземпляра C1, и эта функция объединяет два набора в другом наборе, объединяя их соответствующие потоки и собирая их в новом наборе. –

+1

@LuiggiMendoza Итак, если в карте уже есть отображение '1 -> Set [C2 (1)]' и что вы вставляете отображение '1 -> Set [C2 (2)]', эта функция слияния будет вызываться для создания отображение '1 -> Set [C2 (1), C2 (2)]'. –

3

Похоже, все, что вам нужно сделать, это сгруппировать элементы по общей id, а затем объединения их в один C1:

List<C1> newC1List = c1List.stream() 
     .collect(Collectors.groupingBy(C1::getId/*getters should be implementer*/)) 
     .entrySet() 
     .stream() 
     .map(entry -> /*constructor should be implemented*/ 
       new C1(entry.getKey(), entry.getValue().stream().flatMap(c1 -> c1.getC2Set().stream()).collect(Collectors.toSet()))) 
     .collect(toList()); 

Давайте идти шаг за шагом объяснить этот код:

  1. Первая линия collect создает новый Map, который отображает id на список элементов с этим id.
  2. В следующих двух строках мы создаем Stream пар (Map.Entry на самом деле), в которых ключевым является id, а значение список C1-х, которые имеют этот id.
  3. Следующая операция заключается в объединении всех C1 s от значения до одного C1 (Map.Entry<Integer, List<C1>> -> C1). id берется из ключа записи, хотя я могу использовать идентификатор первого элемента из List, но он выглядит более уродливо :)

    Таким образом, id элемента будет entry.getKey() и c2Set является слияние все Set s внутри C1 s в одном Set. Эта операция легко выполняется с помощью операции flatMapStream (concatMap аналог от Haskell).

  4. Я конвертирую это Stream в List с использованием метода collect.

+1

Работает как и ожидалось. Не могли бы вы дать объяснение читателям о том, что именно происходит в этом коде? –

+0

@ LuiggiMendoza Готово. –