Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collectors.groupingBy (Function, Supplier, Collector) doesn't accept lambda / dosen't see streamed values

I tried to group values using streams and Collectors. I have list of String which I have to split.

My data:

List<String> stringList = new ArrayList<>();
stringList.add("Key:1,2,3")
stringList.add("Key:5,6,7")

Key is a key in map and 1,2,3 are a values in map

First I have tried using simple toMap

Map<String, List<Integer>> outputKeyMap = stringList.stream()
 .collect(Collectors.toMap(id -> id.split(":")[0], 
          id-> Arrays.stream(id.split(":")[1].split(",")).collect(Collectors.toList());

but it dosen't work because it always create the same key. So I need to use groupingBy function.

Map<String, List<Integer>> outputKeyMap = stringList.stream().collect(groupingBy(id -> id.toString().split(":")[0],
            TreeMap::new,
            Collectors.mapping(id-> Arrays.stream(id.toString().split(":")[1].split(","))
                            .map(Integer::valueOf)
                            .collect(Collectors.toSet()))));

But in this solution compiler dosen't see values passed into lambda functions, and I don't why because Function is as first parameter, and also into Collectors.mapping. In this solution stream doesn't work.

Collectors.groupingBy (Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream)

Edit: why groupingBy function doesn't work

I forgot to add Collectors.toSet() in Collectors.mapping as the second parameter. But then I receive Set in Set so it wasn't what am I looking for. There should be used flatMapping but it is in Java9.

    Map<String, Set<Set<String>>> collect = stringList.stream()
                    .collect(groupingBy(id -> id.split(":")[0],
                    TreeMap::new,
                    Collectors.mapping(id-> Arrays.stream(id.toString().split(":")[1].split(","), 
                    Collectors.toSet())
like image 751
Adriano Avatar asked Mar 05 '23 14:03

Adriano


1 Answers

You have to use the overload of Collectors.toMap that accepts a merge function:

Map<String, List<Integer>> result = stringList.stream()
        .map(string -> string.split(":"))
        .collect(Collectors.toMap(
                 splitted -> splitted[0],
                 splitted -> Arrays.stream(splitted[1].split(","))
                                   .map(Integer::valueOf)
                                   .collect(Collectors.toCollection(ArrayList::new)),
                 (l1, l2) -> { l1.addAll(l2); return l1; }));

Here (l1, l2) -> { l1.addAll(l2); return l1; } is the merge function. It will be called by the collector whenever there's a key collision. As List.addAll mutates the list, we need to make sure that the first list that is created is mutable, hence the usage of .collect(Collectors.toCollection(ArrayList::new)) in the value mapper function.

I've also optimized the first splitting to a Stream.map operation that is called before collecting, thus avoiding splitting more than once.


The above solution doesn't remove duplicates from the lists. If you need that, you should collect to a Set instead:

Map<String, Set<Integer>> result = stringList.stream()
        .map(string -> string.split(":"))
        .collect(Collectors.toMap(
                 splitted -> splitted[0],
                 splitted -> Arrays.stream(splitted[1].split(","))
                                   .map(Integer::valueOf)
                                   .collect(Collectors.toCollection(LinkedHashSet::new)),
                 (s1, s2) -> { s1.addAll(s2); return s1; }));

Note that LinkedHashSet preserves insertion order.

like image 172
fps Avatar answered Apr 30 '23 08:04

fps