3

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?

1
  • What are these "keys for each member" ? Commented Dec 10, 2014 at 15:08

2 Answers 2

6
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)));
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the answer. What do you think about this two solutions? The second one is much more easy to understand but the first one seems more functional. BTW, I'm not obsessed with functional programming, just want to know which way is more convenient. :)
I'd lean towards Option 2, TBH.
Without the monster new AbstractMap.SimpleImmutableEntry (and that could be replaced by any other "pair" object), I'd prefer the first one because it's more self-descriptive.
(In that case, I'd write Multimap.entries().stream().collect(Collectors.groupingBy(Entry::getValue, Collectors.summingLong(Entry::getKey))).)
Note also that the former is intrinsically parallelizable, whereas and the latter is likely to be serial (unless your result map tolerates concurrent merges.)
|
0

I will just point out that the code you have posted can be written down much more concisely when 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;
        }
     ));

This still cumbersome, but at least not overwhelmingly so.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.