I am trying to convert Guava Multimap<String ,Collection<String>> into Map<String, Collection<String>> but I get a syntax error when using Multimaps.asMap(multimap). Here is a code:
HashMultimap<String, Collection<String>> multimap = HashMultimap.create();
for (UserDTO dto : employees) {
if (dto.getDepartmentNames() != null) {
multimap.put(dto.getUserName().toString().trim(), dto.getDepartmentNames());
}
}
Map<String, Collection<String>> mapOfSets = Multimaps.asMap(multimap);
Here is a screenshot of error:
Can someone point out where I am doing a mistake?
Return type of Multimaps.asMap(multimap) is Map<String, <Set<Collection<String>>.
Multimap can hold multiple values of the same key. Hence, when you want to convert from multimap to a map, you need to keep collection of values for each key, just in case, there is a key which appears twice in the map.
If you want to convert from MultiMap to Map and make set sum on the values, you can do the following:
Multimaps.asMap(multimap).entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e->e.getValue().stream()
.flatMap(Collection::stream).collect(toSet())));
I think what you're doing here is using Multimap wrong. Multimap<String, Collection<String>> is roughly an equivalent to Map<String, Collection<Collection<String>>>, so it results in nested collections when using asMap view (ex. {user1=[[IT, HR]], user2=[[HR]], user3=[[finance]]}).
What you really want is to use Multimap<String, String> (more specifically: SetMultimap<String, String> which corresponds to Map<String, Set<String>>) and use Multimap#putAll(K, Iterable<V>):
SetMultimap<String, String> multimap = HashMultimap.create();
for (UserDTO dto : employees) {
if (dto.getDepartmentNames() != null) {
// note `.putAll` here
multimap.putAll(dto.getUserName().toString().trim(), dto.getDepartmentNames());
}
}
Map<String, Set<String>> mapOfSets = Multimaps.asMap(multimap);
// ex. {user1=[HR, IT], user2=[HR], user3=[finance]}
Using Multimaps#asMap(SetMultimap) instead of SetMultimap#asMap() is necessary due to Java type system limitation (can't override generic type in a subtype when its nested in a generic type):
Note: The returned map's values are guaranteed to be of type Set. To
obtain this map with the more specific generic type Map<K, Set<V>>,
call Multimaps.asMap(SetMultimap) instead.
Related
I have List of LinkedHashMap like List<Map<String, String>>. Every Map has the same number of elements and every Map has the same keys.
The second element is LinkedHashSet<String> - set of keys.
Now I would like to order every Map from List by keys. Sort ordering is in LinkedHashSet<String>.
My attempt is iterate by List<Map<String, String>>. For every Map create new Map and iterate by Set. To the new Map put key and value from old Mapwhere key is taken from Set. In code:
private List<Map<String, String>> sort(List<Map<String,String> result, LinkedHashSet<String> keys){
List<Map<String, String>> sortedResult = new LinkedList<>();
result.forEach(map -> {
Map<String, String> sortedMap = new LinkedHashMap<>();
keys.forEach(key -> {
sortedMap.put(key, map.get(key));
});
sortedResult.add(sortedMap);
});
return sortedResult;
}
I think it is a little bit complicated and in my opinion there exsists better way to do that.
You have a LinkedHashMap which tries to maintain only the order of insertion of keys, not the natural-ordering of keys. One thing you can do is to maintain a list of keys outside the map and sort them and re-insert the <key,value> pairs as per the order of the sorted list of keys. So it seems you are already doing this in your code by having an order defined by LinkedHashSet.
The other simple approach is:
If you want an ordered map by keys, you most probably need a TreeMap, insertion into this map maintains the natural ordering of keys and you can construct a treemap from an existing map.
private List<Map<String, String>> sort(List<Map<String,String> result) {
List<Map<String, String>> sortedResult = new LinkedList<>();
for ( Map<String, String> m : result )
sortedResult.add(new TreeMap(m)));
return sortedResult;
}
BTW, Local variables referenced from a lambda expression must be final
There are a couple of things I would change:
The argument name "result" is misleading. People going over the code quickly will think this is the returned result. I would change it to "unsortedMaps" or something similar
Each Map shouldn't affect the other so instead of result.forEach you could use result.parallelStream().forEach to make every map sorted in it's own thread. You will need to make insertion to list itself thread safe (either surronding "sortedResult.add(sortedMap" with synchronized statment or use a thread-safe list implementation. All this doesn't guarantee improvement in performance. It depends on many variants such as the size of the collections and number of cores. Test it to find out.
There are a lot of details in this function. I would extract the part dealing with each map to a seperate function
Here is the result (didn't test the code so can't gurentee correctness. Needless to say unit-tests are always the way to go):
private List<Map<String, String>> sort(List<Map<String,String>> unsortedMaps, LinkedHashSet<String> keys){
List<Map<String, String>> sortedResult = new LinkedList<>();
unsortedMaps.parallelStream().forEach(map -> {
Map<String, String> sortedMap = getSortedMap(keys, map);
synchronized (sortedResult) {
sortedResult.add(sortedMap);
}
});
return sortedResult;
}
private Map<String, String> getSortedMap(LinkedHashSet<String> keys, Map<String, String> map) {
Map<String, String> sortedMap = new LinkedHashMap<>();
keys.forEach(key -> {
sortedMap.put(key, map.get(key));
});
return sortedMap;
}
To complete SomeDude answer, if the natural order isn't enough for your need, you can try to specify a Comparator to the TreeMap :
private List<Map<String, String>> sort(List<Map<String,String>> mapList, Set<String> keys){
List<String> keysList = new ArrayList<>(keys);
return mapList.stream().map(map -> copyAndReOrderMap(map, keysList)).collect(Collectors.toList());
}
private Map<String, String> copyAndReOrderMap(Map<String, String> map, List<String> keysList) {
Map<String, String> orderedMap = new TreeMap<>((key1, key2) -> Integer.compare(keysList.indexOf(key1), keysList.indexOf(key2)));
orderedMap.putAll(map);
return orderedMap;
}
NB: Unless you deal with very large maps, i don't see why you would want to sort each map in a separate Thread.
I want to convert from Collection<Map<String,String>> to Map<String,String>.
When I tried to do this way,
Map<String,String> m = (Map<String,String>)map.values();
where,
map is of type Map<String,Map<String,String>>
I'm getting
java.lang.ClassCastException: java.util.TreeMap$Values cannot be cast to java.util.Map
What is it trying to say? I'm not able to get it and how do I correctly convert from Collection<Map<String,String>> to Map<String,String>?
You can use this small snippet to put all the values into a single map:
Map<String, String> result = new TreeMap<>();
for(Map<String, String> value : map.values()) {
result.putAll(value);
}
Though this will just overwrite duplicate keys with a new value if there are any.
As long as it's collection then you should think as it's collection of objects.
Then proceed the iteration, for each object, you shall put it in the map
public Map<String, String> getMapsFromArrayOfMaps( Collection<Map<String,String>> maps ) {
Map<String, String> result = new HashMap<>();
maps.forEach(map->result.putAll(map));
return result ;
}
Is there any way to elegantly initialize and populate a multi-value Map<K,Collection<V>> using Java 8's stream API?
I know it's possible to create a single-value Map<K, V> using the Collectors.toMap(..) functionalities:
Stream<Person> persons = fetchPersons();
Map<String, Person> personsByName = persons.collect(Collectors.toMap(Person::getName, Function.identity()));
Unfortunately, that method won't work well for possibly non-unique keys such as a person's name.
On the other hand, it's possible to populate a multi-value Map<K, Collection<V>> using Map.compute(K, BiFunction<? super K,? super V,? extends V>>):
Stream<Person> persons = fetchPersons();
Map<String, Set<Person>> personsByName = new HashMap<>();
persons.forEach(person -> personsByName.compute(person.getName(), (name, oldValue) -> {
Set<Person> result = (oldValue== null) ? new HashSet<>() : oldValue;
result.add(person);
return result;
}));
Is there no more concise way of doing this, e.g. by initializing and populating the map in one statement?
If you use forEach, it’s much simpler to use computeIfAbsent instead of compute:
Map<String, Set<Person>> personsByName = new HashMap<>();
persons.forEach(person ->
personsByName.computeIfAbsent(person.getName(), key -> new HashSet<>()).add(person));
However, when using the Stream API, it’s preferable to use collect. In this case, use groupingBy instead of toMap:
Map<String, Set<Person>> personsByName =
persons.collect(Collectors.groupingBy(Person::getName, Collectors.toSet());
I want to transform keys in a HashMap. The map has lower_underscore keys but an expected map should have camelCase keys. The map may also have null values.
The straightfoward code to do this is here:
Map<String, Object> a = new HashMap<String, Object>() {{
put("foo_bar", 100);
put("fuga_foga", null); // A value may be null. Collectors.toMap can't handle this value.
}};
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
I want to know the method to do this like Guava's Maps.transformValues() or Maps.transformEntries(), but these methods just transforms values.
Collectors.toMap() is also close, but this method throws NullPointerException when a null value exists.
Map<String, Object> collect = a.entrySet().stream().collect(
Collectors.toMap(x -> toCamel(x.getKey()), Map.Entry::getValue));
If you absolutely want to solve this using streams, you could do it like this:
Map<String, Object> b = a.entrySet()
.stream()
.collect(HashMap::new,
(m, e) -> m.put(toCamel(e.getKey()), e.getValue()),
HashMap::putAll);
But I find the "conventional" way shown in your question easier to read:
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
This is intended as a comment, but got too long for that.
Wanting something like Guava's Maps.transformValues() or Maps.transformEntries() doesn't make too much sense I think.
Those methods return a view of the original map and when you get some
value using a key then the value is transformed by some function that you specified.
(I could be wrong here because I'm not familiar with Guava but I'm making these assumptions based on documentation)
If you wanted to do "transform" the keys then you could do it by writing a wapper for the map like so:
public class KeyTransformingMap<K, V> implements Map {
private Map<K, V> original;
private Function<K, K> reverseTransformer;
public V get(Object transformedKey) {
K originalKey = reverseTransformer.apply((K) transformedKey);
return original.get(originalKey);
}
// delegate all other Map methods directly to original map (or throw UnsupportedOperationException)
}
In your case where you have a map with snake case keys but want camel case keys,
the reverseTransformer function would take in a camel case string and return a snake case string.
I.e reverseTransformer.apply("snakeCase") returns "snake_case" which you can then use as a key for the original map.
Having said all that I think that the straightforward code you suggested is the best option.
This question already has answers here:
How to swap keys and values in a Map elegantly
(9 answers)
Closed 9 years ago.
I want to write a method which will take a map as parameter and replace the keys and values of that map by values and keys. I am trying to do it like this:
public class HashMapKeyValueInterchange{
public static Map<String, String> getMyMap(ConcurrentHashMap<String, String> m){
Map<String, String> map2 = new HashMap<String, String>();
for(Entry<String, String> e:m.entrySet()){
map2.put(e.getValue(), e.getKey());
}
return map2;
}
public static void main(String[] args) {
ConcurrentHashMap<String, String> map1 = new ConcurrentHashMap<String, String>();
map1.put("ajay", "btech");
map1.put("manas", "mca");
map1.put("ashu", "mba");
}
}
Using this method I can get a new map(map2) with exchanged key and values, but I want map1 to be exchanged
It is availabe, no need to reinvent the wheel, if you use google collection library Guava then you can use BiMap<K,V>.
It is a map that preserves the uniqueness of its values as well as
that of its keys. This constraint enables bimaps to support an
"inverse view", which is another bimap containing the same entries as
this bimap but with reversed keys and values.
Implementation of BiMap are EnumBiMap, EnumHashBiMap, [HashBiMap][2], ImmutableBiMap
Use this code
public static ConcurrentHashMap<String, String> getMyMap(ConcurrentHashMap<String, String> m){
ConcurrentHashMap<String, String> map2 = new ConcurrentHashMap<String, String>();
for(Entry<String, String> e:m.entrySet()){
map2.put(e.getValue(), e.getKey());
}
return map2;
}
public static void main(String[] args) {
ConcurrentHashMap<String, String> map1 = new ConcurrentHashMap<String, String>();
map1.put("ajay", "btech");
map1.put("manas", "mca");
map1.put("ashu", "mba");
map1 = getMyMap(map1);
}
If the question is if the code works, the answer is yes. Some comments:
If you want your code tested, you need to call getMyMap with map1 or other adequate parameter.
If you want to see any output, you need to write something like System.out.println( getMyMap( map1 ) );
I'd strongly recommend to use Map instead of ConcurrentHashMap as getMyMap's 1st parameter type, so the function is more general an works with other maps.
To make your code even more general, make it:
public static <K,V> void getMyMap(Map<V,K> output,Map<K,V> input) {
for(Entry<K,V> e: input.entrySet() ) {
output.put(e.getValue(), e.getKey());
}
}
This will accept a bigger variety of Map's and store the output in any type of Map is passed as the first parameter. Example:
getMyMap(new TreeMap<String,String>(),map1);
getMyMap(new HashMap<String,String>(),map1);
A final point is that you don't specify a behavior when values are repeated. The assumption above is that either this case does not occur or that any key in the input map is acceptable as value in the output one.
BiMap is a good choice for this kind of operation
A BiMap is a Map that
allows you to view the "inverse" BiMap with inverse()
ensures that values are unique, making values() a Set
BiMap.put(key, value) will throw an IllegalArgumentException if you attempt to map a key to an already-present value. If you wish to delete any pre-existing entry with the specified value, use BiMap.forcePut(key, value) instead.
BiMap<String, Integer> userId = HashBiMap.create();
String userForId = userId.inverse().get(id);
Look here for more information