Related
I have a list of map of string string(List<Map<String,String>>) like bellow,
[{key = "car", value = "bmw"},{key = "car", value = "suzuki"},{key = "car", value = "hyundai"},{key = "bike", value = "honda"},{key = "bike", value = "tvs"}]
I want to convert to below list of map string list string(List<Map<String, List<String>>>) or something equal
[{key = "car", value = ["bmw","suzuki","hyundai"]},{key = "bike", value = ["honda","tvs"]}]
Thanks in advance.
Java 8+
For singleton maps
Use groupingBy to separate by key and then mapping to get the value of your singleton map
Map<String, List<String>> res =
list.stream()
.map(map -> map.entrySet().iterator().next())
.collect(Collectors.groupingBy(
entry -> entry.getKey(),
Collectors.mapping(
entry -> entry.getValue(),
Collectors.toList()
)
)
);
For maps with multiple entries
You do the same as above, but flatMap to get all the entries in a single 1-D stream
Map<String, List<String>> res =
list.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.groupingBy(
entry -> entry.getKey(),
Collectors.mapping(
entry -> entry.getValue(),
Collectors.toList()
)
)
);
Pre-Java 8
For maps with multiple entries
You'll have to iterate through each map in the list, and then through the entries of each map. For each entry, check if the key's already there, and if so, add the value to the list associated with that key. If the key's not in the result map, then create a new list there.
Map<String, List<String>> map = new HashMap<>();
for (Map<String, String> m : list) {
for (Map.Entry<String, String> e : m.entrySet()) {
String key = e.getKey();
if (!map.containsKey(key)) {
map.put(key, new ArrayList<String>());
}
map.get(key).add(e.getValue());
}
}
For singleton maps
For each map in your original list, you find the first entry, and then you do the same as above: add that entry's value to the list that the entry's key maps to, and before that, add a new List to the map if the key doesn't exist there already.
Map<String, List<String>> map = new HashMap<>();
for (Map<String, String> m : list) {
Map.Entry<String, String> e = m.entrySet().iterator().next();
String key = e.getKey();
if (!map.containsKey(key)) {
map.put(key, new ArrayList<String>());
}
map.get(key).add(e.getValue());
}
Based on your example, you have a list of maps like this:
List<Map<String,String>> myListOfMaps = List.of(
Map.of("car", "bmw"),
Map.of("car", "suzuki"),
Map.of("car", "hyundai"),
Map.of("bike", "honda"),
Map.of("bike", "tvs")
);
Then you can use the groupingBy function of streams to do something similar to what you see below.
Stream your list, flatten the stream by streaming over the entrysets, group the entrysets by their keys, map to a list of values
Map<String,List<String>> myResultMap = myListOfMaps.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
use result/print ..
myResultMap.forEach((k,v)-> {
System.out.println(k + " : " + v);
});
// car : [bmw, suzuki, hyundai]
// bike : [honda, tvs]
I have two lists. I want to create a map which will have true for the matching element and false for the unique one in java 8.
Eg.
Input-
List 1 = [A,B,C,D]
List 2 = [B,C,Y,Z]
Output-
Map:
A,false
B,true
C,true
D,false
My code:
Map<String,Boolean> map = new HashMap<>();
for(String var1 : list1) {
boolean value;
if (CollectionUtils.isNotEmpty(list2)) {
Optional<String> valueOptional = list2.stream()
.filter(e1 -> e1.equalsIgnoreCase(var1))
.findAny();
value = valueOptional.isPresent();
map.put(var1, value);
}
}
First, create a Set using the second list. Then use the toMap collector to create the map which has the string as it's key, and it's existence in the setTwo as the value. Here's how it looks.
Set<String> setTwo = new HashSet<>(listTwo);
Map<String, Boolean> existenceMap = listOne.stream()
.collect(Collectors.toMap(s -> s, setTwo::contains, (a, b) -> a));
I would recommend map with boolean key and values as list
Map<Boolean, List<String>> map = list1.stream()
.collect(Collectors.partitioningBy(list2::contains)); // or set::contains
I am using Java8 to achieve the below things,
Map<String, String> m0 = new HashMap<>();
m0.put("x", "123");
m0.put("y", "456");
m0.put("z", "789");
Map<String, String> m1 = new HashMap<>();
m1.put("x", "000");
m1.put("y", "111");
m1.put("z", "222");
List<Map<String, String>> l = new ArrayList<>(Arrays.asList(m0, m1));
List<String> desiredKeys = Lists.newArrayList("x");
List<Map<String, String>> transformed = l.stream().map(map -> map.entrySet().stream()
.filter(e -> desiredKeys.stream().anyMatch(k -> k.equals(e.getKey())))
.collect(Collectors.toMap(e -> e.getKey(), p -> p.getValue())))
.filter(m -> !m.isEmpty())
.collect(Collectors.toList());
System.err.println(l);
System.err.println(transformed);
List<String> values = new ArrayList<>();
for (Map<String,String> map : transformed) {
values.add(map.values().toString());
System.out.println("Values inside map::"+map.values());
}
System.out.println("values::"+values); //values::[[123], [000]]
Here, I would like to fetch only the x-values from the list. I have achieved it but it is not in a proper format.
Expected output:
values::[123, 000]
Actual output:
values::[[123], [000]]
I know how to fix the actual output. But is there any easy way to achieve this issue? Any help would be appreciable.
You do not need to iterate over the entire map to find an entry by its key. That's what Map.get is for. To flatten the list of list of values, use flatMap:
import static java.util.stream.Collectors.toList;
.....
List<String> values = l.stream()
.flatMap(x -> desiredKeys.stream()
.filter(x::containsKey)
.map(x::get)
).collect(toList());
On a side note, avoid using l (lower case L) as a variable name. It looks too much like the number 1.
I’m not sure Streams will help, here. It’s easier to just loop through the Maps:
Collection<String> values = new ArrayList<>();
for (Map<String, String> map : l) {
Map<String, String> copy = new HashMap<>(map);
copy.keySet().retainAll(desiredKeys);
values.addAll(copy.values());
}
Flat map over the stream of maps to get a single stream representing the map entries of all your input maps. From there, you can filter out each entry whose key is not contained in the desired keys. Finally, extract the equivalent value of each entry to collect them into a list.
final List<String> desiredValues = l.stream()
.map(Map::entrySet)
.flatMap(Collection::stream)
.filter(entry -> desiredKeys.contains(entry.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
EDIT
This assumes that if a map has the key "x" it must also has the key "y" so to fetch the corredponding value.
final List<String> desiredValues = l.stream()
.filter(map -> map.containsKey("x"))
.map(map -> map.get("y"))
.collect(Collectors.toList());
Let's say I have a HashMap with String keys and Integer values:
map = {cat=1, kid=3, girl=3, adult=2, human=5, dog=2, boy=2}
I want to switch the keys and values by putting this information into another HashMap. I know that a HashMap cannot have duplicate keys, so I tried to put the information into a HashMap with the Integer for the keys that would map to a String ArrayList so that I could potentially have one Integer mapping to multiple Strings:
swap = {1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
I tried the following code:
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (String x : map.keySet()) {
for (int i = 0; i <= 5; i++) {
ArrayList<String> list = new ArrayList<String>();
if (i == map.get(x)) {
list.add(x);
swap.put(i, list);
}
}
}
The only difference in my code is that I didn't hard code the number 5 into my index; I have a method that finds the highest integer value in the original HashMap and used that. I know it works correctly because I get the same output even if I hard code the 5 in there, I just didn't include it to save space.
My goal here is to be able to do this 'reversal' with any set of data, otherwise I could just hard code the value. The output I get from the above code is this:
swap = {1=[cat], 2=[boy], 3=[girl], 5=[human]}
As you can see, my problem is that the value ArrayList is only keeping the last String that was put into it, instead of collecting all of them. How can I make the ArrayList store each String, rather than just the last String?
With Java 8, you can do the following:
Map<String, Integer> map = new HashMap<>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
Map<Integer, List<String>> newMap = map.keySet()
.stream()
.collect(Collectors.groupingBy(map::get));
System.out.println(newMap);
The output will be:
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
you are recreating the arrayList for every iteration and i can't figure out a way to do it with that logic, here is a good way though and without the need to check for the max integer:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
List<String> get = swap.get(value);
if (get == null) {
get = new ArrayList<>();
swap.put(value, get);
}
get.add(key);
}
Best way is to iterate over the key set of the original map.
Also you have to asure that the List is present for any key in the target map:
for (Map.Entry<String,Integer> inputEntry : map.entrySet())
swap.computeIfAbsent(inputEntry.getValue(),()->new ArrayList<>()).add(inputEntry.getKey());
This is obviously not the best solution, but approaches the problem the same way you did by interchanging inner and outer loops as shown below.
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (Integer value = 0; value <= 5; value++) {
ArrayList<String> list = new ArrayList<String>();
for (String key : map.keySet()) {
if (map.get(key) == value) {
list.add(key);
}
}
if (map.containsValue(value)) {
swap.put(value, list);
}
}
Output
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
Best way I can think of is using Map.forEach method on existing map and Map.computeIfAbsent method on new map:
Map<Integer, List<String>> swap = new HashMap<>();
map.forEach((k, v) -> swap.computeIfAbsent(v, k -> new ArrayList<>()).add(k));
As a side note, you can use the diamond operator <> to create your new map (there's no need to repeat the type of the key and value when invoking the map's constructor, as the compiler will infer them).
As a second side note, it's good practice to use interface types instead of concrete types, both for generic parameter types and for actual types. This is why I've used List and Map instead of ArrayList and HashMap, respectively.
Using groupingBy like in Jacob's answer but with Map.entrySet for better performance, as suggested by Boris:
// import static java.util.stream.Collectors.*
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(groupingBy(Entry::getValue, mapping(Entry::getKey, toList())));
This uses two more methods of Collectors: mapping and toList.
If it wasn't for these two helper functions, the solution could look like this:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
groupingBy(
Entry::getValue,
Collector.of(
ArrayList::new,
(list, e) -> {
list.add(e.getKey());
},
(left, right) -> { // only needed for parallel streams
left.addAll(right);
return left;
}
)
)
);
Or, using toMap instead of groupingBy:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
toMap(
Entry::getValue,
(e) -> new ArrayList<>(Arrays.asList(e.getKey())),
(left, right) -> {
left.addAll(right);
return left;
}
)
);
It seams you override the values instrad of adding them to the already creared arraylist. Try this:
HashMap<Integer, ArrayList<String>> swapedMap = new HashMap<Integer, ArrayList<String>>();
for (String key : map.keySet()) {
Integer swappedKey = map.get(key);
ArrayList<String> a = swapedMap.get(swappedKey);
if (a == null) {
a = new ArrayList<String>();
swapedMap.put(swappedKey, a)
}
a.add(key);
}
I didn't have time to run it (sorry, don't have Java compiler now), but should be almost ok :)
You could use the new merge method in java-8 from Map:
Map<Integer, List<String>> newMap = new HashMap<>();
map.forEach((key, value) -> {
List<String> values = new ArrayList<>();
values.add(key);
newMap.merge(value, values, (left, right) -> {
left.addAll(right);
return left;
});
});
I have two maps whose keys are Strings and whose values are Set<MyObject>. Given two Maps, what is the easiest way to merge them such that if two keys are identical, the value is a union of the two sets. You can assume values are never null and if it is useful, we can make these Maps SortedMaps.
You can do this with a stream fairly easily:
Map<T, Set<U>> merged = Stream.of(first, second)
.map(Map::entrySet)
.flatMap(Set::stream)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> {
HashSet<U> both = new HashSet<>(a);
both.addAll(b);
return both;
}));
This splits the maps into their Entrys and then joins them with a Collector which resolves duplicates by adding both values to a new HashSet.
This also works for any number of maps.
Some variations which produce the same result:
Stream.of(first, second).flatMap(m -> m.entrySet().stream())
.collect(...);
Stream.concat(first.entrySet().stream(), second.entrySet().stream())
.collect(...); //from comment by Aleksandr Dubinsky
The third parameter for Collectors.toMap is not necessary if there are no duplicate keys.
There is another Collectors.toMap with a fourth parameter that lets you decide the type of the Map collected into.
Are we talking about HashMap instances. In that case lookup is O(1), so you can just take one map, iterate over the entries of that map, see whether the other map contains that key. If not, just add the set. If it contains the key, take the union of the two sets (by adding all elements of one set to another)
To illustrate with some code, where I used a Set to have autocompletion in my IDE
Map<String, Set<Double>> firstMap = new HashMap<String, Set<Double>>( );
Map<String, Set<Double>> secondMap = new HashMap<String, Set<Double>>( );
Set<Map.Entry<String, Set<Double>>> entries = firstMap.entrySet();
for ( Map.Entry<String, Set<Double>> entry : entries ) {
Set<Double> secondMapValue = secondMap.get( entry.getKey() );
if ( secondMapValue == null ) {
secondMap.put( entry.getKey(), entry.getValue() );
}
else {
secondMapValue.addAll( entry.getValue() );
}
}
static void mergeSet(Map<String, Set<String>> map1, Map<String, Set<String>> map2) {
map1.forEach((key1, value1) -> {
map2.merge(key1, value1, (key2, value2) -> key2).addAll(value1);
});
}
How about this (untested):
Map<String,Set<Whatever>> m1 = // input map
Map<String,Set<Whatever>> m2 = // input map
Map<String,Set<Whatever>> ret = // new empty map
ret.putAll(m1);
for(String key : m2.keySet()) {
if(ret.containsKey(key)) {
ret.get(key).addAll(m2.get(key));
} else {
ret.put(key,m2.get(key));
}
}
This solution doesn't modify the input maps, and because it is short and relies on API methods only, I find it quite readable.
Note that putAll() and addAll() are both optional methods in Map and Set. Consequently (and in order to get O(1) lookup), I'd recommend using HashMap and HashSet.
Note that because neither HashSet or HashMap are synchronised you will need to look for some other solution if you want thread-safe code.
The following should merge a map1 into map2 (untested):
for (Entry<String, Set<???>> entry : map1.entrySet( ))
{
Set<???> otherSet = map2.get(entry.getKey( ));
if (otherSet == null)
map2.put(entry.getKey( ), entry.getValue ( ));
else
otherSet.addAll(entry.getValue( ));
}
I don't know what you've parameterized your Sets on, hence the <???>: replace as appropriate.
Something like this (untested):
// Assume all maps are of the same generic type.
public static Map<String, Set<MyObject>> mergeAll(Map m1, Map m2) {
Map<String, Set<MyObject>> merged = new HashMap();
// Merge commom entries into the new map.
for (Map.Entry<String, Set<MyObject>> entry : m1.entrySet()) {
String key = entry.getKey();
Set<MyObject> s1 = new HashSet(entry.getValue());
Set<MyObject> s2 = m2.get(key);
if (s2 != null) s1.addAll(s2);
merged.put(key, s1);
}
// Add entries unique to m2 to the new map.
for (String key : m2.keys()) {
if (!s1.containsKey(key)) merged.put(key, new HashSet(m2.get(key)));
}
return merged;
}
Note that this solution does not mutate either of its arguments.
Map<Integer,String> m1=new HashMap<Integer,String>();
Map<Integer,String> m2=new HashMap<Integer,String>();
m1.put(1,"one");
m1.put(2,"two");
m2.put(3,"three");
m2.put(2,"two");
Set<Integer> s=m2.keySet();
for(int i:s){
if(m1.get(i)==null){
m1.put(i,m2.get(i));
}
}
System.out.println(m1);
Note that all other answers will eventually augment the original sets which you might not want for all use cases, if you don't want that just use a third map as output and create a new set for each key
public static void merge2Maps(Map<String, Set<Double>> a, Map<String, Set<Double>> b, Map<String, Set<Double>> c){
for (Map.Entry<String, Set<Double>> entry : a.entrySet()) {
Set<Double> set = new HashSet<Double>();
c.put(entry.getKey(), set);
set.addAll(entry.getValue());
}
for (Map.Entry<String, Set<Double>> entry : b.entrySet()) {
String key = entry.getKey();
Set<Double> set = c.get(key);
if (set == null) {
set = new HashSet<Double>();
c.put(entry.getKey(), set);
}
set.addAll(entry.getValue());
}
}
If you want to end up with immutable data structures to prevent manipulation of your merged map and map's Set instances then you can take this approach. This solution uses Google's Guava library.
public <K,T> Map<K, Set<T>> mergeToImmutable (
final Map<K, Set<T>> left,
final Map<K, Set<T>> right)
{
return Maps.toMap(
Sets.union(
checkNotNull(left).keySet(),
checkNotNull(right).keySet()
),
new Function<K, Set<T>> () {
#Override
public Set<T> apply (K input) {
return ImmutableSet.<T>builder()
.addAll(MoreObjects.firstNonNull(left.get(input), Collections.<T>emptySet()))
.addAll(MoreObjects.firstNonNull(right.get(input), Collections.<T>emptySet()))
.build();
}
}
);
}
If you define a method to unite non-null Sets as:
static <T> Set<T> union(Set<T>... sets) {
return Stream.of(sets)
.filter(s -> s != null)
.flatMap(Set::stream)
.collect(Collectors.toSet());
}
then merging two maps m1 and m2 having Set<V> values can be performed as follows:
Map<String, V> merged
= union(m1.keySet(), m2.keySet())
.stream()
.collect(Collectors.toMap(k -> k, k -> union(m1.get(k), m2.get(k))));
Or even simpler:
Map<String, V> merged = new HashMap<>();
for (String k : union(m1.keySet(), m2.keySet())
merged.put(k, union(m1.get(k), m2.get(k)));
<K, V> Map<K, List<V>> mergeMapOfLists(Stream<Map<K, List<V>>> stream) {
return stream
.map(Map::entrySet) // convert each map to set of map's entries
.flatMap(Collection::stream) // convert each map entry to stream and flat them to one stream
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue,
(list1, list2) -> {
list1.addAll(list2);
return list1;
})); // convert stream to map; if key is duplicated execute merge fuction (append exisitng list with elements from new list)
}