Converting a map to another map using the Stream API
I have Map<Long, List<Member>>()
, and I want to create Map<Member, Long>
, which is calculated by iterating List<Member>
in Map.Entry<Long, List<Member>>
and summing the keys of each map entry for each member in that member list. It's easy without a non-functional way, but I couldn't find a way without writing a custom collector using Java 8 Stream API. It seems to me that I need something like Stream.collect(Collectors.toFlatMap)
, however, Collectors
there is no such method.
The best way I could find is this:
longListOfMemberMap = new HashMap<Long, List<Member>>()
longListOfMemberMap.put(10, asList(member1, member2));
Map<Member, Long> collect = longListOfMemberMap.entrySet().stream()
.collect(new Collector<Map.Entry<Long, List<Member>>, Map<Member, Long>, Map<Member, Long>>() {
@Override
public Supplier<Map<Member, Long>> supplier() {
return HashMap::new;
}
@Override
public BiConsumer<Map<Member, Long>, Map.Entry<Long, List<Member>>> accumulator() {
return (memberLongMap, tokenRangeListEntry) -> tokenRangeListEntry.getValue().forEach(member -> {
memberLongMap.compute(member, new BiFunction<Member, Long, Long>() {
@Override
public Long apply(Member member, Long aLong) {
return (aLong == null ? 0 : aLong) + tokenRangeListEntry.getKey();
}
});
});
}
@Override
public BinaryOperator<Map<Member, Long>> combiner() {
return (memberLongMap, memberLongMap2) -> {
memberLongMap.forEach((member, value) -> memberLongMap2.compute(member, new BiFunction<Member, Long, Long>() {
@Override
public Long apply(Member member, Long aLong) {
return aLong + value;
}
}));
return memberLongMap2;
};
}
@Override
public Function<Map<Member, Long>, Map<Member, Long>> finisher() {
return memberLongMap -> memberLongMap;
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.UNORDERED);
}
});
// collect is equal to
// 1. member1 -> 10
// 2. member2 -> 10
The code in the example takes a Map> as parameter and creates a map:
parameter Map<Long, List<Member>>
:
// 1. 10 -> list(member1, member2)
collected value Map<Member, Long>
:
// 1. member1 -> 10
// 2. member2 -> 10
However, as you can see, this is much uglier than non-functional. I tried Collectors.toMap
and minified the method Stream
, but couldn't find a way to do multiple lines of code.
Which way would be the easiest and most functional for this problem?
source to share
longListOfMemberMap.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(
member ->
new AbstractMap.SimpleImmutableEntry<>(member, entry.getKey())))
.collect(Collectors.groupingBy(
Entry::getKey,
Collectors.summingLong(Entry::getValue)));
... although an even simpler but more imperative alternative might look like
Map<Member, Long> result = new HashMap<>();
longListOfMemberMap.forEach((val, members) ->
members.forEach(member -> result.merge(member, val, Long::sum)));
source to share
I'll just point out that the code you posted can be written much more succinctly by relying on Collector.of
and turning your anonymous classes into lambdas:
Map<Member, Long> result = longListOfMemberMap.entrySet().stream()
.collect(Collector.of(
HashMap::new,
(acc, item) -> item.getValue().forEach(member -> acc.compute(member,
(x, val) -> Optional.ofNullable(val).orElse(0L) + item.getKey())),
(m1, m2) -> {
m1.forEach((member, val1) -> m2.compute(member, (x, val2) -> val1 + val2));
return m2;
}
));
It's still cumbersome, but at least not overwhelmingly.
source to share