Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting a Map to another Map using Stream API

I have a Map<Long, List<Member>>() and I want to produce a Map<Member, Long> that is calculated by iterating that 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 non-functional way but I couldn't find a way without writing a custom collector using Java 8 Stream API. I think I need something like Stream.collect(Collectors.toFlatMap) however there is no such method in Collectors.

The best way that I could found is like 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 produces 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 see it's much more ugly than the non-functional way. I tried Collectors.toMap and reduce method of Stream but I couldn't find a way to do with a few lines of code.

Which way would be the simplest and functional for this problem?

like image 958
Boyolame Avatar asked Feb 26 '26 22:02

Boyolame


1 Answers

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

...though 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)));
like image 67
Louis Wasserman Avatar answered Feb 28 '26 11:02

Louis Wasserman