Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 streams reduce and combine List items to Map

I need to create map with keys of name1/2 summing values value1/2.

What could be the cleanest way to rewrite this using java 8 streams?

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}
like image 512
karolkpl Avatar asked Apr 25 '16 09:04

karolkpl


1 Answers

A possible solution is to flat map each item into a stream made by two entries: each entry will be composed of the name and the corresponding value. Then, this is collected into a map by summing the values of the values having the same key. Since there is no built-in pair to hold both values, we can use AbstractMap.SimpleEntry.

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));

Alternatively, it might be simpler to just use a custom collect call. The accumulator merges each key, just like what you have in the for loop body. The tricky part is the combiner, which in this case merges two maps together by iterating over the entries of the second map and merging them into the first one.

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))
            );
like image 61
Tunaki Avatar answered Sep 21 '22 20:09

Tunaki