2016-04-25 7 views
4

Ich brauche Karte mit Schlüsseln von name1/2 Summenwerte Wert1/2 zu erstellen.Java 8 Streams reduzieren und kombinieren Listenelemente zu Karte

Was könnte der sauberste Weg sein, dies mit Java 8 Streams neu zu schreiben?

class Item { 

    private String name1; 
    private Integer value1; 
    private String name2; 
    private Integer value2; 

    public Item(final String name1, final Integer value1, final String name2, final Integer value2) { 
     this.name1 = name1; 
     this.value1 = value1; 
     this.name2 = name2; 
     this.value2 = value2; 
    } 
    //getters and setters 
} 
List<Item> list = Lists.newArrayList(
       new Item("aaa", 1, "bbb", 2), 
       new Item("bbb", 5, "ccc", 3), 
       new Item("aaa", 8, "bbb", 7), 
       new Item("bbb", 2, "aaa", 5)); 

Map<String, Integer> map = Maps.newHashMap(); 

for (Item item : list) { 
    map.merge(item.name1, item.value1, Integer::sum); 
    map.merge(item.name2, item.value2, Integer::sum); 
} 

System.out.println(map);//{aaa=14, ccc=3, bbb=16} 

Antwort

7

Eine mögliche Lösung ist die flache Karte jedes Element in eine von zwei Eingaben stream: Jeder Eintrag wird aus dem Namen und dem entsprechenden Wert zusammengesetzt sein. Dann wird dies in einer Karte durch Summieren der Werte der Werte mit demselben Schlüssel gesammelt. Da es kein eingebautes Paar gibt, das beide Werte enthält, können wir AbstractMap.SimpleEntry verwenden.

Map<String, Integer> map = 
    list.stream() 
     .flatMap(i -> Stream.of(new AbstractMap.SimpleEntry<>(i.name1, i.value1), new AbstractMap.SimpleEntry<>(i.name2, i.value2))) 
     .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum)); 

Alternativ könnte es einfacher sein, nur einen benutzerdefinierten collect Aufruf zu verwenden. Der Akkumulator führt jeden Schlüssel zusammen, genau wie in dem for Loop-Körper. Der heikle Teil ist der Combiner, der in diesem Fall zwei Maps zusammenführt, indem er über die Einträge der zweiten Map iteriert und diese in die erste Map zusammenführt.

Map<String, Integer> map = 
     list.stream() 
      .collect(
       HashMap::new, 
       (m, i) -> { 
        m.merge(i.name1, i.value1, Integer::sum); 
        m.merge(i.name2, i.value2, Integer::sum); 
       }, 
       (m1, m2) -> m2.forEach((k, v) -> m1.merge(k, v, Integer::sum)) 
      ); 
+1

Ich frage nur nach Neugier. Warum benutzt du 'reduce' anstelle von' collect'? Gibt es irgendwelche Vorteile? – Flown

+1

@Flown Nein, es ist wirklich das Gleiche. Der 'flatMap'-Aufruf ist wegen der Notwendigkeit, einen Tupelhalter zu haben, nicht so einfach (so könnte es schwieriger zu verstehen sein). 'reduce' ist nur eine alternative Lösung, die mehr wie die 'for'-Schleife liest. Es kann einen Unterschied in der Leistung geben, wenn die Liste ziemlich groß ist, nicht sicher. – Tunaki

+3

Die Frage zielte auf Ihren zweiten Ansatz und ich fand eine Antwort in den Oracle-Tutorials. 'reduce' wird in einer unveränderlichen und" collect "veränderbaren Reduktion verwendet. In der sequentiellen Ausführung zeigen beide Reduktionen das gleiche Verhalten. Wenn Sie parallelisierten 'Stream' verwenden möchten, wäre es besser,' collect' zu verwenden: 'list.stream(). Collect (HashMap :: neu, (m, i) -> {m.merge (i.name1, i .value1, Integer :: sum); m.merge (i.name2, i.value2, Integer :: sum);}, (m1, m2) -> m1.fürEach ((k, v) -> m2.merge (k, v, Integer :: sum))) ' – Flown