Best way to map keys of a Map - java

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.

Related

How to convert Guava HashMultmap to java.util.Map

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.

Flatten a Map<Integer, List<String>> to Map<String, Integer> with stream and lambda

I would like to flatten a Map which associates an Integer key to a list of String, without losing the key mapping.
I am curious as though it is possible and useful to do so with stream and lambda.
We start with something like this:
Map<Integer, List<String>> mapFrom = new HashMap<>();
Let's assume that mapFrom is populated somewhere, and looks like:
1: a,b,c
2: d,e,f
etc.
Let's also assume that the values in the lists are unique.
Now, I want to "unfold" it to get a second map like:
a: 1
b: 1
c: 1
d: 2
e: 2
f: 2
etc.
I could do it like this (or very similarly, using foreach):
Map<String, Integer> mapTo = new HashMap<>();
for (Map.Entry<Integer, List<String>> entry: mapFrom.entrySet()) {
for (String s: entry.getValue()) {
mapTo.put(s, entry.getKey());
}
}
Now let's assume that I want to use lambda instead of nested for loops. I would probably do something like this:
Map<String, Integer> mapTo = mapFrom.entrySet().stream().map(e -> {
e.getValue().stream().?
// Here I can iterate on each List,
// but my best try would only give me a flat map for each key,
// that I wouldn't know how to flatten.
}).collect(Collectors.toMap(/*A String value*/,/*An Integer key*/))
I also gave a try to flatMap, but I don't think that it is the right way to go, because although it helps me get rid of the dimensionality issue, I lose the key in the process.
In a nutshell, my two questions are :
Is it possible to use streams and lambda to achieve this?
Is is useful (performance, readability) to do so?
You need to use flatMap to flatten the values into a new stream, but since you still need the original keys for collecting into a Map, you have to map to a temporary object holding key and value, e.g.
Map<String, Integer> mapTo = mapFrom.entrySet().stream()
.flatMap(e->e.getValue().stream()
.map(v->new AbstractMap.SimpleImmutableEntry<>(e.getKey(), v)))
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
The Map.Entry is a stand-in for the nonexistent tuple type, any other type capable of holding two objects of different type is sufficient.
An alternative not requiring these temporary objects, is a custom collector:
Map<String, Integer> mapTo = mapFrom.entrySet().stream().collect(
HashMap::new, (m,e)->e.getValue().forEach(v->m.put(v, e.getKey())), Map::putAll);
This differs from toMap in overwriting duplicate keys silently, whereas toMap without a merger function will throw an exception, if there is a duplicate key. Basically, this custom collector is a parallel capable variant of
Map<String, Integer> mapTo = new HashMap<>();
mapFrom.forEach((k, l) -> l.forEach(v -> mapTo.put(v, k)));
But note that this task wouldn’t benefit from parallel processing, even with a very large input map. Only if there were additional computational intense task within the stream pipeline that could benefit from SMP, there was a chance of getting a benefit from parallel streams. So perhaps, the concise, sequential Collection API solution is preferable.
You should use flatMap as follows:
entrySet.stream()
.flatMap(e -> e.getValue().stream()
.map(s -> new SimpleImmutableEntry(e.getKey(), s)));
SimpleImmutableEntry is a nested class in AbstractMap.
Hope this would do it in simplest way. :))
mapFrom.forEach((key, values) -> values.forEach(value -> mapTo.put(value, key)));
This should work. Please notice that you lost some keys from List.
Map<Integer, List<String>> mapFrom = new HashMap<>();
Map<String, Integer> mapTo = mapFrom.entrySet().stream()
.flatMap(integerListEntry -> integerListEntry.getValue()
.stream()
.map(listItem -> new AbstractMap.SimpleEntry<>(listItem, integerListEntry.getKey())))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
Same as the previous answers with Java 9:
Map<String, Integer> mapTo = mapFrom.entrySet()
.stream()
.flatMap(entry -> entry.getValue()
.stream()
.map(s -> Map.entry(s, entry.getKey())))
.collect(toMap(Entry::getKey, Entry::getValue));

Java8: Map<X, Y> to Map<X, Z> using RxJava

I know how to transform Map<X, Y> to Map<X, Z> using stream and it there was also a previous question about this.
But I want to learn how do it using RxJava.
And to be more specific:
//Input:
Map<String,String> in; // map from key to a string number, for example: "10"
//transform: in -> out
//Output:
Map<String,Integer> out; //map from the same key to Integer.parseInt(val) (invariant: val is a string number)
I think Dávid Karnok is right in a sense that it is not particularly useful case for RxJava if your only goal is to convert a single map.
It might make more sense if you have Observable of (multiple) maps.
Then I would recommend to use a convenience of Guava transformers: https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/collect/Maps.html#transformValues(java.util.Map,%20com.google.common.base.Function)
Map<String,String> source1 = ...
Map<String,String> source2 = ...
Observable.just(source1, source2)
.map(map->Maps.transformValues(map, v->Integer.parseInt(v)));
One nice thing about Guava's map value transformer is that it does not recreate map data structure, but instead creates a transformed view which maps values lazily (on the fly).
If you do not want to depend on Guava, you can just trivially implement Func1<Map<String,String>,Map<String,Integer>> yourself.
You can use Observable.toMap.
I think this is a minimal example:
Map<String, String> test = new HashMap<String, String>() {
{
put("1", "1");
put("2", "2");
}
};
Map<String, Integer> res = Observable.from(test.entrySet()).toMap(e -> e.getKey(), e -> Integer.parseInt(e.getValue())).toBlocking().first();

Map same key and value using google collections

I need to validate if map (String to String) entry doesn't contain same key and value pair (case-insensitive). For example -
("hello", "helLo") // is not a valid entry
I was wondering if Google collection's Iterable combined with Predicates some how could solve this problem easily.
Yes I could have simple iterator for entries to do it myself, but thinking of any thing already up.
Looking for something in-lined with Iterables.tryFind(fromToMaster, Predicates.isEqualEntry(IGNORE_CASE)).isPresent()
If you want to use guava, you can use the Maps utils, specifically the filterEntries function.
An example to filter only entries where the key does not equal the value (ignoring the case) could look like this
Map<String, String> map = new HashMap<>();
map.put("hello", "helLo");
map.put("Foo", "bar");
Map<String, String> filtered = Maps.filterEntries(map, new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return !input.getKey().equalsIgnoreCase(input.getValue());
}
});
System.out.println(filtered); // will print {Foo=bar}
However there is no default Predicate in guava's Predicates I know of that does what you want.
Addition:
If you want a validation mechanism without creating a new map, you can use Iterables and the any method to iterate over the entry set of the map. To make the condition more readable I would assign the predicate to a variable or a member field of the class you are working in.
Predicate<Map.Entry<String, String>> keyEqualsValueIgnoreCase = new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return input.getKey().equalsIgnoreCase(input.getValue());
}
};
if (Iterables.any(map.entrySet(), keyEqualsValueIgnoreCase)) {
throw new IllegalStateException();
}
or if you need the entry, you can use the Iterables#tryFind method and use the returned Optional
Optional<Map.Entry<String, String>> invalid = Iterables.tryFind(map.entrySet(), keyEqualsValueIgnoreCase);
if(invalid.isPresent()) {
throw new IllegalStateException("Invalid entry " + invalid.get());
}

Converting Map<String,String> to Map<String,Object>

I have Two Maps
Map<String, String> filterMap
Map<String, Object> filterMapObj
What I need is I would like to convert that Map<String, String> to Map<String, Object>.
Here I am using the code
if (filterMap != null) {
for (Entry<String, String> entry : filterMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
Object objectVal = (Object)value;
filterMapObj.put(key, objectVal);
}
}
It works fine, Is there any other ways by which I can do this without iterating through all the entries in the Map.
Instead of writing your own loop that calls put, you can putAll, which does the same thing:
filterMapObj.putAll(filterMap);
(See the Javadoc.)
And as Asanka Siriwardena points out in his/her answer, if your plan is to populate filterMapObj immediately after creating it, then you can use the constructor that does that automatically:
filterMapObj = new HashMap<>(filterMap);
But to be clear, the above are more-or-less equivalent to iterating over the map's elements: it will make your code cleaner, but if your reason for not wanting to iterate over the elements is actually a performance concern (e.g., if your map is enormous), then it's not likely to help you. Another possibility is to write:
filterMapObj = Collections.<String, Object>unmodifiableMap(filterMap);
which creates an unmodifiable "view" of filterMap. That's more restrictive, of course, in that it won't let you modify filterMapObj and filterMap independently. (filterMapObj can't be modified, and any modifications to filterMap will affect filterMapObj as well.)
You can use the wildcard operator for this.
Define filterMapObj as Map<String, ? extends Object> filterMapObj and you can directly assign the filterMap to it. You can learn about generics wildcard operator
You can simply write
Map<String, Object> filterMapObj = new HashMap<>(filterMap);
You can use putAll method to solve the problem.The Object is the father class of all objects,so you can use putAll without convert.

Categories