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

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();

Related

How to keep transformation result for all subsequent stages in reactor

Let's say I have a Reactor stream that consists of 4 stages:
Mono.just(event)
.map(this::map1)
.map(this::map2)
.map(this::map3)
.map(this::map4)
I want the result of this::map1 be accessible by this::map2, this::map3 and this::map4 stages.
Is there any simple way to do this with Reactor?
I think the simplest way of solving this is considering that you need to introduce some sort of "boundary" around map1. This can be achieved by a flatMap:
Mono.just(event)
.map(this::map1)
.flatMap(v1 -> Mono.just(v1)
.map(v2 -> map2(v2, v1))
.map(v3 -> map3(v3, v1))
.map(v4 -> map4(v4, v1))
);
NB: I assumed you couldn't merge the different map functions together for some reason, like simplification of the snippet
I would merge your map1, map2, map3 and map4, in a single map function, since the 4 functions depend on one another
But if you insist on using 4 seperate functions, you could pass the context along the reactive stream using a tuple, for example :
private Tuple2<String, HashMap> map3(Tuple2<String, HashMap> inputTuple) {
String input = inputTuple.getT1();
HashMap context = inputTuple.getT2();
// mapping example
String result = input + context.get("result1") + "mappingExample";
context.put("result3", result);
return Tuples.of(result, context);
}
Or just a simple map that holds all your results, for example :
private HashMap<String, String> map3(HashMap<String, String> input) {
String result3 = input.get("result2") + input.get("result1");
input.put("result3", result3);
return input;
}

Idiomatically creating a multi-value Map from a Stream in Java 8

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());

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));

Best way to map keys of a Map

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.

Java8: HashMap<X, Y> to HashMap<X, Z> using Stream / Map-Reduce / Collector

I know how to "transform" a simple Java List from Y -> Z, i.e.:
List<String> x;
List<Integer> y = x.stream()
.map(s -> Integer.parseInt(s))
.collect(Collectors.toList());
Now I'd like to do basically the same with a Map, i.e.:
INPUT:
{
"key1" -> "41", // "41" and "42"
"key2" -> "42" // are Strings
}
OUTPUT:
{
"key1" -> 41, // 41 and 42
"key2" -> 42 // are Integers
}
The solution should not be limited to String -> Integer. Just like in the List example above, I'd like to call any method (or constructor).
Map<String, String> x;
Map<String, Integer> y =
x.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey(),
e -> Integer.parseInt(e.getValue())
));
It's not quite as nice as the list code. You can't construct new Map.Entrys in a map() call so the work is mixed into the collect() call.
Here are some variations on Sotirios Delimanolis' answer, which was pretty good to begin with (+1). Consider the following:
static <X, Y, Z> Map<X, Z> transform(Map<? extends X, ? extends Y> input,
Function<Y, Z> function) {
return input.keySet().stream()
.collect(Collectors.toMap(Function.identity(),
key -> function.apply(input.get(key))));
}
A couple points here. First is the use of wildcards in the generics; this makes the function somewhat more flexible. A wildcard would be necessary if, for example, you wanted the output map to have a key that's a superclass of the input map's key:
Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<CharSequence, Integer> output = transform(input, Integer::parseInt);
(There is also an example for the map's values, but it's really contrived, and I admit that having the bounded wildcard for Y only helps in edge cases.)
A second point is that instead of running the stream over the input map's entrySet, I ran it over the keySet. This makes the code a little cleaner, I think, at the cost of having to fetch values out of the map instead of from the map entry. Incidentally, I initially had key -> key as the first argument to toMap() and this failed with a type inference error for some reason. Changing it to (X key) -> key worked, as did Function.identity().
Still another variation is as follows:
static <X, Y, Z> Map<X, Z> transform1(Map<? extends X, ? extends Y> input,
Function<Y, Z> function) {
Map<X, Z> result = new HashMap<>();
input.forEach((k, v) -> result.put(k, function.apply(v)));
return result;
}
This uses Map.forEach() instead of streams. This is even simpler, I think, because it dispenses with the collectors, which are somewhat clumsy to use with maps. The reason is that Map.forEach() gives the key and value as separate parameters, whereas the stream has only one value -- and you have to choose whether to use the key or the map entry as that value. On the minus side, this lacks the rich, streamy goodness of the other approaches. :-)
A generic solution like so
public static <X, Y, Z> Map<X, Z> transform(Map<X, Y> input,
Function<Y, Z> function) {
return input
.entrySet()
.stream()
.collect(
Collectors.toMap((entry) -> entry.getKey(),
(entry) -> function.apply(entry.getValue())));
}
Example
Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<String, Integer> output = transform(input,
(val) -> Integer.parseInt(val));
Guava's function Maps.transformValues is what you are looking for, and it works nicely with lambda expressions:
Maps.transformValues(originalMap, val -> ...)
Does it absolutely have to be 100% functional and fluent? If not, how about this, which is about as short as it gets:
Map<String, Integer> output = new HashMap<>();
input.forEach((k, v) -> output.put(k, Integer.valueOf(v));
(if you can live with the shame and guilt of combining streams with side-effects)
My StreamEx library which enhances standard stream API provides an EntryStream class which suits better for transforming maps:
Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();
An alternative that always exists for learning purpose is to build your custom collector through Collector.of() though toMap() JDK collector here is succinct (+1 here) .
Map<String,Integer> newMap = givenMap.
entrySet().
stream().collect(Collector.of
( ()-> new HashMap<String,Integer>(),
(mutableMap,entryItem)-> mutableMap.put(entryItem.getKey(),Integer.parseInt(entryItem.getValue())),
(map1,map2)->{ map1.putAll(map2); return map1;}
));
If you don't mind using 3rd party libraries, my cyclops-react lib has extensions for all JDK Collection types, including Map. We can just transform the map directly using the 'map' operator (by default map acts on the values in the map).
MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
.map(Integer::parseInt);
bimap can be used to transform the keys and values at the same time
MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
.bimap(this::newKey,Integer::parseInt);
Although it is possible to remap the key or/and value in the collect part of the stream as shown in other answers, I think it should belong in the map part as that function is designed to transform the data within the stream. Next to that it should be easily repeatable without introducing additional complexity. The SimpleEntry object can be used which is already available since Java 6.
With Java 8
import java.util.AbstractMap.SimpleEntry;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
Map<String, String> x;
Map<String, Integer> y = x.entrySet().stream()
.map(entry -> new SimpleEntry<>(entry.getKey(), Integer.parseInt(entry.getValue())))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
}
With Java 9+
With the release of Java 9 a static method within the Map interface was introduced to make it easier to just create an entry without to instantiate a new SimpleEntry as shown in the previous example.
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
Map<String, String> x;
Map<String, Integer> y = x.entrySet().stream()
.map(entry -> Map.entry((entry.getKey(), Integer.parseInt(entry.getValue())))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
}
A declarative and simpler Java8+ solution would be :
yourMap.replaceAll((key, val) -> computeNewVal);
Cheers to :
http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/

Categories