Convert Map of maps into Lists using Java 8 Streams - java

I have a map:
Map<String, Map<Integer, List<Integer>>>
e.g. Map<Name, Map<Id, List<ReferenceId>>>
Outcome:
List<Id>
List<ReferenceId>
I wanna convert this map into two list of Integers. One list contains inner-map keys, and other contains inner-map value (i.e. List<Integer>)
Can anyone tell me how to do this in Java 8 using streams?
I tried this way but got Cast Exception, can not convert String to Integer.
map.values().stream()
.map(m -> m.entrySet()
.stream()
.map(e -> e.getKey())
.collect(Collectors.toList()))
.flatMap(l -> l.stream())
.collect(Collectors.toList());

Map<String, Map<Integer, List<Integer>>> map = ...
List<Integer> keys = map.values() // Collection<Map<Integer, List<Integer>>>
.stream() // Stream<Map<Integer, List<Integer>>>
.map(Map::keySet) // Stream<Set<Integer>>
.flatMap(Set::stream) // Stream<Integer>
.collect(Collectors.toList()); // List<Integer>
List<Integer> values = map.values() // Collection<Map<Integer, List<Integer>>>
.stream() // Stream<Map<Integer, List<Integer>>>
.map(Map::values) // Stream<Collection<List<Integer>>>
.flatMap(Collection::stream) // Stream<List<Integer>>
.flatMap(List::stream) // Stream<Integer>
.collect(Collectors.toList()); // List<Integer>

There is no way, how your code
List<Integer> list = map.values().stream()
.map(m -> m.entrySet().stream()
.map(e -> e.getKey())
.collect(Collectors.toList()))
.flatMap(l -> l.stream())
.collect(Collectors.toList());
can produce a ClassCastException, unless you managed to insert objects of wrong type into the source map via unchecked operation(s) before the Stream operation. Such a situation is called heap pollution and you should compile your entire code with all warnings enabled (javac: use option -Xlint:unchecked) and solve them.
But note that your code is unnecessarily complicated. The chain, .entrySet().stream().map(e -> e.getKey()) is streaming over the entries and mapping to the keys, so you can stream over the keys in the first place, i.e. .keySet().stream(). Then, you are collecting the stream into a List, just to invoke .stream() in the subequent flatMap step, so you can simply use the stream you already have instead:
List<Integer> list = map.values().stream()
.flatMap(m -> m.keySet().stream())
.collect(Collectors.toList());
Alternatively, you can let the collector do all the work:
List<Integer> list = map.values().stream()
.collect(ArrayList::new, (l,m) -> l.addAll(m.keySet()), List::addAll);
Getting the values instead of the keys works similar, but requires another flatMap step to get the List elements:
List<Integer> list = map.values().stream()
.flatMap(m -> m.values().stream().flatMap(List::stream))
.collect(Collectors.toList());
which is equivalent to
List<Integer> list = map.values().stream()
.flatMap(m -> m.values().stream())
.flatMap(List::stream)
.collect(Collectors.toList());
Again, there’s the alternative of letting the collector do all the work:
List<Integer> list = map.values().stream()
.collect(ArrayList::new, (l,m)->m.values().forEach(l::addAll), List::addAll);
or
List<Integer> list = map.values().stream()
.collect(ArrayList::new, (l,m)->m.forEach((k,v)->l.addAll(v)), List::addAll);

If your value is like Map<String,Object>. And your Object is like Map<String,Object>:
Set<String> mapKeys = myMap.entryset() //Set<Entry<String,Object>>
.stream() //Stream<Entry<String,Object>>
.map(m->(Map<String,Object>) m.getValue()) //Stream<Map<String,Object>>
.map(Map::keySet) //Stream<Set<String>>
.flatMap(l->l.stream()) //Stream<String>
.collect(Collectors.toSet())
it works

Adding for one more working scenario
Map<Integer, List<Integer>> existingPacakagesMap = // having value {123=[111, 222, 333], 987=[444, 555, 666]}
Retrieving logic
List<Integer> ListOfAllPacakages= existingPacakagesMap.values().stream().flatMap(List::stream).collect(Collectors.toList());
Result will be
ListOfAllPacakages= [111, 222, 333, 444, 555, 666]

Related

Why is map stream sorted after filtering not working?

I have Map<String,Integer> map. I want to filter and sort the map by key and then get 5 percent of their number , I have such a function:
public List<String> getValuableSubStr(){
List<String> result = new ArrayList<>();
long size = map.entrySet().stream().filter(e -> e.getKey().length() ==3).count();
map.entrySet().stream()
.filter(e -> e.getKey().length() ==3)
.sorted(Map.Entry.<String,Integer>comparingByKey().reversed())
.limit((size*5)/100)
.peek(e -> result.add(e.getKey()));
return result;
}
But after calling the function , I get an empty list , although the map is not empty and forEach are printed normally .What did I do wrong?
peek is not a terminal operation, so it doesn't cause the stream to be evaluated.
Use forEach instead of peek.
Or, better, collect directly into a list.
return map.entrySet().stream()
.filter(e -> e.getKey().length() ==3)
.sorted(Map.Entry.<String,Integer>comparingByKey().reversed())
.limit((size*5)/100)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
Actually, since you're dealing only with keys:
return map.keySet().stream()
.filter(e -> e.length() ==3)
.sorted(Comparator.reverseOrder())
.limit((size*5)/100)
.collect(Collectors.toList());
peek is more useful for debugging. See here:
it's an intermediate operation and we didn't apply a terminal operation
from https://www.baeldung.com/java-streams-peek-api
I would do
public List<String> getValuableSubStr(){
List<String> result = new ArrayList<>();
long size = map.entrySet().stream().filter(e -> e.getKey().length() ==3).count();
return map.entrySet().stream()
.filter(e -> e.getKey().length() ==3)
.sorted(Map.Entry.<String,Integer>comparingByKey().reversed())
.limit((size*5)/100)
.map(a -> a.getKey())
.collect(Collectors.toList());
}

How to split a string into a map, grouping values by duplicate keys using streams?

I want to convert the following
String flString="view1:filedname11,view1:filedname12,view2:fieldname21";
to a Map<String,Set<String>> to get the key/value as below:
view1=[filedname11,filedname12]
view2=[fieldname21]
I want to use Java 8 streams. I tried
Arrays.stream(tokens)
.map(a -> a.split(":"))
.collect(Collectors.groupingBy(
a -> a[0], Collectors.toList()));
However the keys are also getting added to the value list.
You should use a Collectors::mapping to map the array to an element.
String flString = "view1:filedname11,view1:filedname12,view2:fieldname21";
Map<String, List<String>> map = Pattern.compile(",")
.splitAsStream(flString)
.map(a -> a.split(":"))
.collect(
Collectors.groupingBy(a -> a[0],
Collectors.mapping(a -> a[1], Collectors.toList())
)
);
map.entrySet().forEach(System.out::println);
Output
view1=[filedname11, filedname12]
view2=[fieldname21]
You can use Collectors#toMap(keyMapper,valueMapper,mergeFunction) method as follows:
String flString = "view1:filedname11,view1:filedname12,view2:fieldname21";
Map<String, Set<String>> map = Arrays
.stream(flString.split(","))
.map(str -> str.split(":"))
.collect(Collectors.toMap(
arr -> arr[0],
arr -> new HashSet<>(Set.of(arr[1])),
(s1, s2) -> {
s1.addAll(s2);
return s1;
}));
System.out.println(map);
// {view1=[filedname11, filedname12], view2=[fieldname21]}
Off course #K.Nicholas solution with using downstream collector is perfect.
But I additionally created another alternative solution that also uses Stream API.
It is more complex and generates three streams, but it works too.
String flString = "view1:filedname11,view1:filedname12,view2:fieldname21";
Map<String, Set<String>> map1 =
Arrays.stream(flString.split(","))
.map(a -> a.split(":"))
.collect(Collectors.toMap(
a -> a[0],
a -> a[1],
(a1, a2) -> a1 + "," + a2))
.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey(),
e -> Arrays.stream(e.getValue().split(","))
.collect(Collectors.toSet())));
map1.entrySet().forEach(System.out::println);
You can achieve your goal using the following piece of codes:
String flString = "view1:filedname11,view1:filedname12,view2:fieldname21";
Map<String, List<String>> collect = Stream.of(flString)
.flatMap(str -> Stream.of(str.split(",")))
.map(keyValuePair -> keyValuePair.split(":"))
.collect(Collectors.groupingBy(it -> it[0], Collectors.mapping(it -> it[1], Collectors.toList())));
Simple output:
{view1=[filedname11, filedname12], view2=[fieldname21]}

How can I flatten a List<List<List<String>>>

I am trying to flatten a Big List:
List<List<List<String>>> input
Example of my list:
[[[a,b],[c,b]], [[x],[y]]]`
I want the result to be as follows:
[[a,b,c],[x,y]]
For the duplicates, I will try to use LinkedHashSet, but I can not flatten the list.
List<List<String>> result =
list.stream()
.map(x -> x.stream()
.flatMap(List::stream)
.distinct()
.collect(Collectors.toList()))
.collect(Collectors.toList());
You can do like this:
List<Set<String>> result = new ArrayList<>();
lists.forEach(list -> result.add(list.stream()
.flatMap(List::stream)
.collect(Collectors.toSet())));
or
lists.stream()
.collect(
() -> new ArrayList<Set<String>>(),
(sets, l) -> sets.add(l.stream()
.flatMap(List::stream)
.collect(Collectors.toSet())),
List::addAll);
Not much different, and much simple as in:
List<List<List<String>>> input = List.of(
List.of(List.of("a", "b"), List.of("c", "b")),
List.of(List.of("x"), List.of("y"))
);
List<LinkedHashSet<String>> output = input.stream()
.map(l -> l.stream()
.flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new)))
.collect(Collectors.toList());

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())
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.
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]}

Split list of string to list of map

I have the following list
[12_223,13_4356,15_5676]
I was able to spilt on underscore and convert this into one Hashmap using the below code
list.stream()
.map(s -> s.split("_"))
.collect(Collectors.toMap(
a -> a[0],
a -> a[1]));
it gives the below map
{"12"="223", "13"="4356", "15"="5676"}
But I wanted to change this code so that it gives me a list of maps like below as I might encounter duplicate keys while splitting
[{"12"="223"}, {"13"="4356"}, {"15"="5676"}]
If you really want the result to be a list of maps, I'd do it this way:
List<Map<String, String>> listOfMaps(List<String> list) {
return list.stream()
.map(s -> s.split("_", 2))
.map(a -> Collections.singletonMap(a[0], a[1]))
.collect(toList());
}
(HT: Eugene for recommending split("_", 2).)
In Java 9, you could do it this way:
List<Map<String, String>> listOfMaps(List<String> list) {
return list.stream()
.map(s -> s.split("_", 2))
.map(a -> Map.of(a[0], a[1]))
.collect(toList());
}
In both cases, the map instances in the resulting list will be unmodifiable.
This does what you are looking for
List<Map<String, String>> output = list.stream().map(
s-> {
Map<String, String> element = new HashMap<>();
String[] arr = s.split("_");
element.put(arr[0], arr[1]);
return element;
}).collect(Collectors.toList());
But as #Pshemo suggests, this should be more appropriate
Map<String, List<String>> outputnew = list.stream().map(
s -> s.split("_"))
.collect(Collectors.groupingBy(s -> s[0],
Collectors.mapping(s -> s[1], Collectors.toList())));

Categories

Resources