1

I am trying to use the streams api groupingby collector to get a mapping groupId -> List of elements. The special thing about my case is that an element can belong to more than one group.

To demonstrate it with a simple example: suppose I want to use the numbers 2 - 10 as identifier for the grouping and want to group the numbers 2 - 40 so that they can be seen as multiples of my identifier. Traditionally I would do it like this:

Map<Integer,List<Integer>> map = new HashMap<>();
    for(int i = 2; i < 11; i++){
        for(int j = 2; j < 41; j++){
            if(j%i == 0)
            map.computeIfAbsent(i, k -> new ArrayList<>()).add(j);
        }
    }
    map.forEach((k,v) -> {
        System.out.println(k + " : " + v);
    });

and get something like

2 : [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40]
3 : [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39]
4 : [4, 8, 12, 16, 20, 24, 28, 32, 36, 40]
5 : [5, 10, 15, 20, 25, 30, 35, 40]
6 : [6, 12, 18, 24, 30, 36]
7 : [7, 14, 21, 28, 35]
8 : [8, 16, 24, 32, 40]
9 : [9, 18, 27, 36]
10 : [10, 20, 30, 40]

To do it with streams I tried to apply the answers to this question to my case, but without success.

IntStream.range(2, 11).boxed()
            .flatMap(g -> IntStream.range(2, 41)
                .boxed()
                .filter(i -> i%g == 0)
                .map(i -> new AbstractMap.SimpleEntry<>(g,i))
            .collect(Collectors.groupingBy(Map.Entry::getKey, 
                    Collectors.mapping(Map.Entry::getValue, Collectors.toList()))));

I get a compile error

incompatible types: inference variable R#1 has incompatible bounds equality constraints: Map<K,D> lower bounds: Stream<? extends R#2>,Object where R#1,A#1,T#1,K,T#2,A#2,D,R#2 are type-variables: R#1 extends Object declared in method <R#1,A#1>collect(Collector<? super T#1,A#1,R#1>) A#1 extends Object declared in method <R#1,A#1>collect(Collector<? super T#1,A#1,R#1>) T#1 extends Object declared in interface Stream K extends Object declared in method <T#2,K,A#2,D>groupingBy(Function<? super T#2,? extends K>,Collector<? super T#2,A#2,D>) T#2 extends Object declared in method <T#2,K,A#2,D>groupingBy(Function<? super T#2,? extends K>,Collector<? super T#2,A#2,D>) A#2 extends Object declared in method <T#2,K,A#2,D>groupingBy(Function<? super T#2,? extends K>,Collector<? super T#2,A#2,D>) D extends Object declared in method <T#2,K,A#2,D>groupingBy(Function<? super T#2,? extends K>,Collector<? super T#2,A#2,D>) R#2 extends Object declared in method <R#2>flatMap(Function<? super T#1,? extends Stream<? extends R#2>>)


what am I doing wrong?

Please note that my original case is not the assignment of numbers to their multiples. In reality my group ids have long values and the list contains custom objects. But when I get my above example solved I think I can apply it to my case. I just wanted to describe the problem in a simple way

nopens
  • 721
  • 1
  • 4
  • 20
  • 1
    Your original code with `map.computeIfAbsent` is, by far, much better than the stream version. It's also better in terms of performance and readability/expresiveness – fps Oct 30 '20 at 12:57
  • @fps You are right. But I am taking a course for junior developers and I have to come up with a solution using streams. – nopens Oct 30 '20 at 13:06

1 Answers1

1

You meant smthg like this?

Map<Integer,List<Integer>> v = IntStream.range(2, 11).boxed()
               .map(g -> IntStream.range(2, 41)
                       .boxed()
                       .filter(i -> i % g == 0)
                       .map(i -> new AbstractMap.SimpleEntry<>(g, i))
                       .collect(Collectors.groupingBy(AbstractMap.SimpleEntry::getKey,
                               Collectors.mapping(AbstractMap.SimpleEntry::getValue, Collectors.toList()))))
               .flatMap(m -> m.entrySet().stream())
               .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Curiosa Globunznik
  • 3,129
  • 1
  • 16
  • 24
  • Thank you for your response, but I need the resulting map to be of type `Map> ` – nopens Oct 30 '20 at 12:42
  • Your suggestion was indeed helpful. I replaced the last `.collect(Collectors.toList());` with `.flatMap(m -> m.entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));` and got the desired result. Thanks – nopens Oct 30 '20 at 12:59
  • Was just a quick hack, but I think, it can be done in a much simpler way. ah, it's not so bad after all. – Curiosa Globunznik Oct 30 '20 at 13:26