问题
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())
回答1:
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.
回答2:
Assuming you don't have duplicate keys in your source list, you can get Map<String, List<Integer>>
like:
Map<String, List<Integer>> result = stringList.stream()
.collect(toMap(string -> string.split(":")[0],
string -> Arrays.stream(string.split(":")[1].split(","))
.map(Integer::valueOf)
.collect(toList())));
If you have duplicate keys, there is a way with flatMapping
from java9:
Map<String, List<Integer>> result = stringList.stream()
.collect(groupingBy(s -> s.split(":")[0],
flatMapping(s -> Arrays.stream(s.split(":")[1].split(","))
.map(Integer::valueOf),
toList())));
The output will contain all integer values for Key
:
{Key=[1, 2, 3, 5, 6, 7]}
来源:https://stackoverflow.com/questions/54520174/collectors-groupingby-function-supplier-collector-doesnt-accept-lambda-do