Forgive me if the solution is very obvious but I can't seem to figure out how to do this
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("b1", "a1");
map.put("b2", "a2");
map.put("b3", "a1");
Map<String, List<String>> mm = map.values().stream().collect(Collectors.groupingBy(m -> m));
System.out.println(mm);
}
I want to group by based on values in hashmap. I want the output to be {a1=[b1, b3], a2=[b2]} but it is currently coming as {a1=[a1, a1], a2=[a2]}
Currently, you're streaming over the map values (which I assume is a typo), based on your required output you should stream over the map entrySet and use groupingBy based on the map value's and mapping as a downstream collector based on the map key's:
Map<String, List<String>> result = map.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey,
Collectors.toList())));
You could also perform this logic without a stream via forEach + computeIfAbsent:
Map<String, List<String>> result = new HashMap<>();
map.forEach((k, v) -> result.computeIfAbsent(v, x -> new ArrayList<>()).add(k));
You can use Collectors.mapping with Collectors.groupingBy on the entrySet of the map as :
Map<String, List<String>> mm = map.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
but it is currently coming as {a1=[a1, a1], a2=[a2]}
That's because you are currently grouping on the values collection which is {a1, a2, a1} only.
public class Test5 {
public static void main(String[] args) {
List<String> list1 = List.of("Tabu", "Gina", "protijayi", "Gini", "Gini","North Calcutta");
List<String> list2 = List.of("Soudipta", "Gina", "Gina", "upto");
List<String> list3 = List.of("Soudipta", "Gina", "protijayi", "Tabu","South Calcutta");
List<List<String>> listres = List.of(list1, list2, list3);
System.out.println(listres);
/*
[[Tabu, Gina, protijayi, Gini, Gini, North Calcutta],
[Soudipta, Gina, Gina, upto],
[Soudipta, Gina, protijayi, Tabu, South Calcutta]]
*/
Map<String, List<Long>> FirstKeyThenValue1 = listres.stream().flatMap(
// mapper
list -> list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().parallelStream()
).collect(Collectors.groupingBy(Entry::getKey, Collectors.mapping(
// mapper, downstream
Entry::getValue, Collectors.toList())));
System.out.println("FirstKeyThenValue1 -> " + FirstKeyThenValue1);
/*
{
upto=[1],
Soudipta=[1, 1],
Gina=[1, 2, 1],
Tabu=[1, 1],
North Calcutta=[1],
South Calcutta=[1],
protijayi=[1, 1],
Gini=[2]}
*/
Map<Long, List<String>> FirstValueThenkey1 = listres.stream().flatMap(
// mapper
list -> list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
).collect(
Collectors.groupingBy(Entry::getValue, Collectors.mapping(
Entry::getKey, Collectors.toList()
))
);
System.out.println(" FirstValueThenkey1 => " + FirstValueThenkey1);
/*
{
1=[Gina, Tabu, North Calcutta, protijayi, upto, Soudipta,
Soudipta, Gina, Tabu, South Calcutta, protijayi],
2=[Gini, Gina]
}
*/
}// main
}
Related
I've a Map<KeyString, List<MyVO>>.
MyVO.java contains:
String name;
int id;
I want to map it into Map<KeyString, List<names from MyVO>.
How can achieve this using java 8 streams?
You can use something like this:
Map<String, List<String>> response =
map.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream()
.map(MyVO::getName)
.collect(Collectors.toList())));
I'm using a record to demo. A class would also work here.
record VO(String getStr)
}
first create some data
Map<String, List<VO>> map =
Map.of("A", List.of(new VO("S1"), new VO("S2")), "B",
List.of(new VO("S3"), new VO("S4")));
map.entrySet().forEach(System.out::println);
prints
A=[VO[str=S1], VO[str=S2]]
B=[VO[str=S3], VO[str=S4]]
now stream the entry set of the original map
and collect using Collectors.toMap.
use the orginal key from the Entry.
and stream the list of VO to pull out the string and create a new list.
Map<String,List<String>> result = map.entrySet().stream()
.collect(Collectors.toMap(
Entry::getKey,
e -> e.getValue().stream().map(VO::getStr).toList()));
prints
A=[S1, S2]
B=[S3, S4]
A solution:
public static void mapNames() {
final Map<String, List<MyVO>> voMap = new HashMap<>();
voMap.put("all", Arrays.asList(
new MyVO(1, "John"),
new MyVO(2, "Bill"),
new MyVO(3, "Johanna")
));
final Map<String, List<String>> nameMap = voMap.entrySet().stream()
.filter(Objects::nonNull)
.collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream()
.map(MyVO::getName)
.collect(Collectors.toList())
));
System.out.println(nameMap);
}
Output:
{all=[John, Bill, Johanna]}
I need to find a Hashmap whose is_default value is equal to 1.
Noted: In this structure, only 1 hashmap contains is_default value is equal to 1.
Try this.
public static void main(String[] args) {
List<Map<String, String>> list = List.of(
Map.of("is_default", "0", "label", "test0"),
Map.of("is_default", "1", "label", "test1"),
Map.of("is_default", "2", "label", "test2"));
Map<String, String> extracted = list.stream()
.filter(map -> Objects.equals(map.get("is_default"), "1"))
.findFirst().orElse(null);
System.out.println(extracted);
}
output:
{label=test1, is_default=1}
You only need to check those maps that contain the required key. After that, you need to choose from them the one with the desired value of this key. It can be done with filter
list.stream()
.filter(x -> x.containsKey("is_default"))
.filter(x -> x.get("is_default")
.compareTo(BigDecimal.ONE) == 0)
.forEach(System.out::println);
Or you can use getOrDefault and specify wrong value as default to skip those maps that do not contain a key
list.stream()
.filter(x -> x.getOrDefault("is_default", BigDecimal.ZERO)
.compareTo(BigDecimal.ONE) == 0)
.forEach(System.out::println);
Example
public static void main(String[] args) {
List<Map<String, BigDecimal>> list = new ArrayList<>();
// target
Map<String, BigDecimal> map = new HashMap<>();
map.put("is_default", BigDecimal.ONE);
map.put("key", BigDecimal.TEN);
list.add(map);
Map<String, BigDecimal> map2 = new HashMap<>();
map2.put("is_default", BigDecimal.ZERO);
map2.put("key", BigDecimal.ZERO);
list.add(map2);
Map<String, BigDecimal> map3 = new HashMap<>();
map3.put("isnt_default", BigDecimal.ZERO);
list.add(map3);
list.stream()
.filter(x -> x.containsKey("is_default"))
.filter(x -> x.get("is_default")
.compareTo(BigDecimal.ONE) == 0)
.forEach(System.out::println);
}
Output
{is_default=1, key=10}
I'd like to know how I can insert a map into another map using streams in java.
I have a two maps
Map<String, List<Character>> map1
Map<String, List<Integer>> map2
I d like to merge both maps such that we have
Map<String, Map<Character, Integer>> finalmap
if map1 is something like
{String1 = [Character1, Character2], String2 = [Character3, Character4], etc}
and map2 is
{String1 = [Integer1, Integer2], String2 = [Integer3, Integer4], etc}
I want it to merge such that the innermap maps Character1 with Integer1 and so on.
Does someone have an idea how to solve this problem? :)
Map<String, Map<Character, Integer>> map3 = map1.entrySet()
.stream()
.flatMap(entry -> {
if (map2.containsKey(entry.getKey())) {
List<Integer> integers = map2.get(entry.getKey());
List<Character> characters = entry.getValue();
Map<Character, Integer> innerMap = IntStream.range(0, Math.min(integers.size(), characters.size()))
.mapToObj(i -> Map.entry(characters.get(i), integers.get(i)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return Stream.of(Map.entry(entry.getKey(), innerMap));
}
return Stream.empty();
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
This is a bit late but in case the length of Character and Integer lists is different, it may be possible to build an inner map containing all Character keys and null for missing Integer values:
// class MyClass
static Map<String, Map<Character, Integer>> joinMaps(
Map<String, List<Character>> map1, Map<String, List<Integer>> map2)
{
return map1
.entrySet()
.stream()
.filter(e -> map2.containsKey(e.getKey())) // keep the keys from both maps
.map(e -> Map.entry(
e.getKey(), // String key for result
IntStream.range(0, e.getValue().size()) // build map <Character, Integer>
.mapToObj(i -> new AbstractMap.SimpleEntry<>(
e.getValue().get(i),
i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
))
// use custom collector to allow null Integer values
.collect(
MyClass::innerMap,
(hm, e2) -> hm.put(e2.getKey(), e2.getValue()),
Map::putAll
)
))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
static LinkedHashMap<Character, Integer> innerMap() {
return new LinkedHashMap<>();
}
Custom collector for inner map is needed to allow adding null values which is not possible with Collectors.toMap where NPE is thrown.
Tests:
Map<String, List<Character>> map1 = Map.of(
"S0", Arrays.asList('#', '#'),
"S1", Arrays.asList('a', 'b'),
"S2", Arrays.asList('c', 'd'),
"S3", Arrays.asList('e', 'f')
);
System.out.println("map1=" + map1);
Map<String, List<Integer>> map2 = Map.of(
"S1", Arrays.asList(1),
"S2", Arrays.asList(3, 4, 5),
"S3", Arrays.asList(5, 6),
"S4", Arrays.asList(7, 8)
);
System.out.println("map2=" + map2);
Map<String, Map<Character, Integer>> res = joinMaps(map1, map2);
System.out.println("----\nResult:");
res.forEach((k, v) -> System.out.printf("%s -> %s%n", k, v));
Output:
map1={S0=[#, #], S1=[a, b], S2=[c, d], S3=[e, f]}
map2={S1=[1], S2=[3, 4, 5], S3=[5, 6], S4=[7, 8]}
----
Result:
S1 -> {a=1, b=null}
S2 -> {c=3, d=4}
S3 -> {e=5, f=6}
Update
Another solution using Stream::flatMap and groupingBy + mapping collectors with a custom collector used as a downstream collector is shown below:
static Map<String, Map<Character, Integer>> joinMaps(
Map<String, List<Character>> map1, Map<String, List<Integer>> map2)
{
return map1
.entrySet()
.stream()
.filter(e -> map2.containsKey(e.getKey()))
.flatMap(e -> IntStream.range(0, e.getValue().size())
.mapToObj(i -> Map.entry(
e.getKey(),
new AbstractMap.SimpleEntry<>(
e.getValue().get(i),
i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
)
))) // Stream<Map.Entry<String, Map.Entry<Character, Integer>>>
.collect(Collectors.groupingBy(
Map.Entry::getKey, // use String as key in outer map
Collectors.mapping(e -> e.getValue(), // build inner map
Collector.of( // using custom collector
MyClass::innerMap, // supplier
(hm, e2) -> hm.put(e2.getKey(), e2.getValue()), // accumulator
MyClass::mergeMaps // combiner
))
));
}
static <T extends Map> T mergeMaps(T acc, T map) {
acc.putAll(map);
return acc;
}
Here mergeMaps is a BinaryOperator<A> combiner argument in the method Collector.of which slightly differs from Stream::collect used above where BiConsumer<R, R> is used as combiner.
I have two maps like this:
map1 = new Map<String, MyObject>();
map2 = new Map<String, MyObject>();
MyObject {
Integer mark1;
Integer mark2;
}
What I want to do to is to merge the two maps into a map3 <String, MyObject> like this:
If map1.place is not in map2.place, then I add the entry to map3.
same if map2.place is not in map1.place, I add the entry to map3.
if map1.place is in map2.place, then I add this entry:
map1.place, (map1.mark1, map2.mark2)
I have read about flatMap, but I really have a hard time using it.
Any clue how to do this?
It can be done using the Stream API with the appropriate mergeFunction as next:
Map<String, MyObject> map3 = Stream.of(map1, map2)
.flatMap(map -> map.entrySet().stream())
.collect(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> new MyObject(v1.getMark1(), v2.getMark2())
)
);
This concatenates entries of map1 followed by the entries of map2, then convert everything as a Map with a merge function that will use mark1 from the first value (the one from map1) and mark2 from the second value (the one from map2) in case of duplicate keys.
Or it could also be done using a different Supplier<Map> that will propose a map that already contains the entries of map1 then we can focus only on adding the entries of map2 as next:
Map<String, MyObject> map3 = map2.entrySet()
.stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> new MyObject(v1.getMark1(), v2.getMark2()),
() -> new HashMap<>(map1)
)
);
Here is what I think would work
Map<String, MyObj> map3 = new HashMap<>(map1);
map2.forEach(
(key, value) -> map3.merge(key, value, (v1, v2) -> new MyObject(v1.mark1,v2.mark2))
);
The merge function is what is taking care of your scenario 3, in that if the key already exists, it creates a new MyObject with v1.mark1 and v2.mark2
Something like this should work.
Map<String, MyObject> result = new HashMap<String, MyObject>();
Set<String> allKeys = new HashSet<String>();
allKeys.addAll(map1.keySet());
allKeys.addAll(map2.keySet());
for(String key : allKeys){
MyObject v1 = map1.get(key);
MyObject v2 = map2.get(key);
if(v1 != null && v2 == null){
result.put(key, v1);
}else if(v1 == null && v2 !=null){
result.put(key, v2);
} else {
MyObject newObject = new MyObject(v1.mark1, v2.mark2);
result.put(key, newObject);
}
}
Incase of a simple merge you could use map3.putAll() as explained in How can I combine two HashMap objects containing the same types?
In your case, you would probably have to write some custom logic,
First populate map3 with map1. Then Iterate the map3 to find any duplicates with map2 in which case you replace the entry with the map1.place, (map1.mark1, map2.mark2) logic.
MapMerge
public class MapMerge {
public static void main(String []args){
Map<String, MyObject> map1 = new HashMap<String, MyObject>();
Map<String, MyObject> map2 = new HashMap<String, MyObject>();
Map<String, MyObject> map3 = new HashMap<String, MyObject>();
map3.putAll(map1);
for(Entry<String, MyObject> entry:map2.entrySet()){
if (map3.containsKey(entry.getKey())){
MyObject map3Obj = map3.get(entry.getKey());
map3.put(
entry.getKey(),
new MyObject(map3Obj.getMark1(),entry.getValue().getMark2())
);
}
}
}
}
MyObject
class MyObject{
public MyObject(Integer m1, Integer m2){
mark1 = m1;
mark2 = m2;
}
public Integer getMark1() {
return mark1;
}
public void setMark1(Integer mark1) {
this.mark1 = mark1;
}
public Integer getMark2() {
return mark2;
}
public void setMark2(Integer mark2) {
this.mark2 = mark2;
}
Integer mark1;
Integer mark2;
}
Case 1: Given a List of Maps. Then Join the maps according to the Key
public class Test14 {
public static void main(String[] args) {
Map<String, List<Integer>> m1 = new HashMap<>();
Map<String, List<Integer>> m2 = new HashMap<>();
m1.put("a", List.of(1));
m1.put("b", List.of(2, 3));
m2.put("a", List.of(12, 115));
m2.put("b", List.of(2, 5));
m2.put("c", List.of(6));
System.out.println("map1 => " + m1);
System.out.println("map2 => " + m2);
ArrayList<Map<String, List<Integer>>> maplist = new ArrayList<Map<String, List<Integer>>>();
maplist.add(m1);
// map1 => {a=[1], b=[2, 3]}
maplist.add(m2);
// map2 => {a=[12, 115], b=[2, 5], c=[6]}
System.out.println("maplist => " + maplist);
// maplist => [{a=[1], b=[2, 3]}, {a=[12, 115], b=[2, 5], c=[6]}]
// flatmap does omitted {}
List<Entry<String, List<Integer>>> collect11 =
maplist
.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toList());
System.out.println(" collect11 => " + collect11);
// collect11 => [a=[1], b=[2, 3], a=[12, 115], b=[2, 5], c=[6]]
// That's why we will use this flatmap
Map<String, List<Integer>> map2 = maplist.stream()
.flatMap(map -> map.entrySet().stream())
.collect(
Collectors.toMap(
//keyMapper
Map.Entry::getKey,
//valueMapper
Map.Entry::getValue,
(list_a,list_b) -> Stream.concat(list_a.stream(), list_b.stream())
.collect(Collectors.toList())
)//tomap
);
//{a=[1, 12, 115], b=[2, 3, 2, 5], c=[6]}
System.out.println("After joining the maps according the key => " + map2);
// After joining the maps according the key => {a=[1, 12, 115], b=[2, 3, 2, 5], c=[6]}
/*
OUTPUT :
After joining the maps according the key => {a=[1, 12, 115], b=[2, 3, 2, 5], c=[6]}
*/
}// main
}
I'm trying to learn Java 8 Stream and when I try to convert some function to java8 to practice. I meet a problem.
I'm curious that how can I convert follow code to java stream format.
/*
* input example:
* [
{
"k1": { "kk1": 1, "kk2": 2},
"k2": {"kk1": 3, "kk2": 4}
}
{
"k1": { "kk1": 10, "kk2": 20},
"k2": {"kk1": 30, "kk2": 40}
}
]
* output:
* {
"k1": { "kk1": 11, "kk2": 22},
"k2": {"kk1": 33, "kk2": 44}
}
*
*
*/
private static Map<String, Map<String, Long>> mergeMapsValue(List<Map<String, Map<String, Long>>> valueList) {
Set<String> keys_1 = valueList.get(0).keySet();
Set<String> keys_2 = valueList.get(0).entrySet().iterator().next().getValue().keySet();
Map<String, Map<String, Long>> result = new HashMap<>();
for (String k1: keys_1) {
result.put(k1, new HashMap<>());
for (String k2: keys_2) {
long total = 0;
for (Map<String, Map<String, Long>> mmap: valueList) {
Map<String, Long> m = mmap.get(k1);
if (m != null && m.get(k2) != null) {
total += m.get(k2);
}
}
result.get(k1).put(k2, total);
}
}
return result;
}
The trick here is to collect correctly the inner maps. The workflow would be:
Flat map the list of map List<Map<String, Map<String, Long>>> into a stream of map entries Stream<Map.Entry<String, Map<String, Long>>>.
Group by the key of each of those entry, and for the values mapped to same key, merge the two maps together.
Collecting maps by merging them would ideally warrant a flatMapping collector, which unfortunately doesn't exist in Java 8, although it will exist in Java 9 (see JDK-8071600). For Java 8, it is possible to use the one provided by the StreamEx library (and use MoreCollectors.flatMapping in the following code).
private static Map<String, Map<String, Long>> mergeMapsValue(List<Map<String, Map<String, Long>>> valueList) {
return valueList.stream()
.flatMap(e -> e.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.flatMapping(
e -> e.getValue().entrySet().stream(),
Collectors.<Map.Entry<String,Long>,String,Long>toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum)
)
));
}
Without using this convenient collector, we can still build our own with equivalent semantics:
private static Map<String, Map<String, Long>> mergeMapsValue2(List<Map<String, Map<String, Long>>> valueList) {
return valueList.stream()
.flatMap(e -> e.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collector.of(
HashMap::new,
(r, t) -> t.getValue().forEach((k, v) -> r.merge(k, v, Long::sum)),
(r1, r2) -> { r2.forEach((k, v) -> r1.merge(k, v, Long::sum)); return r1; }
)
));
}
As a starting point, converting to use computeIfAbsent and merge gives us the following:
private static <K1, K2> Map<K1, Map<K2, Long>> mergeMapsValue(List<Map<K1, Map<K2, Long>>> valueList) {
final Map<K1, Map<K2, Long>> result = new HashMap<>();
for (final Map<K1, Map<K2, Long>> map : valueList) {
for (final Map.Entry<K1, Map<K2, Long>> sub : map.entrySet()) {
for (final Map.Entry<K2, Long> subsub : sub.getValue().entrySet()) {
result.computeIfAbsent(sub.getKey(), k1 -> new HashMap<>())
.merge(subsub.getKey(), subsub.getValue(), Long::sum);
}
}
}
return result;
}
This removes much of the logic from your inner loop.
This code below is wrong, I leave it here for reference.
Converting to the Stream API is not going to make it neater, but lets give it a go.
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
private static <K1, K2> Map<K1, Map<K2, Long>> mergeMapsValue(List<Map<K1, Map<K2, Long>>> valueList) {
return valueList.stream()
.flatMap(v -> v.entrySet().stream())
.collect(groupingBy(Entry::getKey, collectingAndThen(mapping(Entry::getValue, toList()), l -> l.stream()
.reduce(new HashMap<>(), (l2, r2) -> {
r2.forEach((k, v) -> l2.merge(k, v, Long::sum);
return l2;
}))));
}
This is what I've managed to come up with - it's horrible. The problem is that with the foreach approach, you have a reference to each level of the iteration - this makes the logic simple. With the functional approach, you need to consider each folding operation separately.
How does it work?
We first stream() our List<Map<K1, Map<K2, Long>>>, giving a Stream<Map<K1, Map<K2, Long>>>. Next we flatMap each element, giving a Stream<Entry<K1, Map<K2, Long>>> - so we flatten the first dimension. But we cannot flatten further as we need to K1 value.
So we then use collect(groupingBy) on the K1 value giving us a Map<K1, SOMETHING> - what is something?
Well, first we use a mapping(Entry::getValue, toList()) to give us a Map<K1, List<Map<K2, Long>>>. We then use collectingAndThen to take that List<Map<K2, Long>> and reduce it. Note that this means we produce an intermediate List, which is wasteful - you could get around this by using a custom Collector.
For this we use List.stream().reduce(a, b) where a is the initial value and b is the "fold" operation. a is set to new HashMap<>() and b takes two values: either the initial value or the result of the previous application of the function and the current item in the List. So we, for each item in the List use Map.merge to combine the values.
I would say that this approach is more or less illegible - you won't be able to decipher it in a few hours time, let alone a few days.
I took the flatMap(e -> e.entrySet().stream()) part from Tunaki, but used a shorter variant for the collector:
Map<String, Integer> merged = maps.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
More elaborate example:
Map<String, Integer> a = new HashMap<String, Integer>() {{
put("a", 2);
put("b", 5);
}};
Map<String, Integer> b = new HashMap<String, Integer>() {{
put("a", 7);
}};
List<Map<String, Integer>> maps = Arrays.asList(a, b);
Map<String, Integer> merged = maps.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
assert merged.get("a") == 9;
assert merged.get("b") == 5;