Convert Java Map Enum keys to Map String keys - java

I have the following map:
Map<DataFields, String> myMap;
But I need to convert it to the following:
Map<String, String> myMap;
My best feeble attempt which doesn't even compile is:
myMap.keySet().stream().map(k -> k.name()).collect(Collectors.toMap(k, v)

You need to stream the entrySet() (so you have both the keys and the values), and collect them to a map:
Map<String, String> result =
myMap.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getKey().name(), e -> e.getValue()));

Map<String, String> result = myMap
.entrySet() // iterate over all entries (object with tow fields: key and value)
.stream() // create a stream
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue()));
// collect to map: convert enum Key value toString() and copy entry value

Another way of doing same without Collectors helper. Using entryset will make it very easy to map.
map.entrySet()
.stream()
.collect(
() -> new HashMap<String, String>(),
(Map newMap, Map.Entry<DataFields, String> entry) -> {
newMap.put(entry.getKey().name(), entry.getValue());
}
,
(Map map1, Map map2) -> {
map.putAll(map2);
}
);

A Java 8, succint way to do it (without streams):
Map<String, String> result = new HashMap<>();
myMap.forEach((k, v) -> result.put(k.name(), v));

Related

Collecting a basic map in Java 8 streams API

Map<String, String> preFilteredKafkaRecords = kafkaRecordMap.entrySet().stream()
.map(item -> getUrls(item.getKey(), item.getValue()))
.filter(THING_1)
.filter(THING_2)
.collect(Collectors.toMap(
Map.Entry<String, String>::getKey, Map.Entry<String, String>::getValue));
getUrls - returns Map<String, String>
how can I collect this to a Map<String, String>? The first map returns a Map<String, String> but I can't get the compiler to stop complaining.
If getUrls() returns a Map, you can't collect it as an Entry. If the idea is to combine all the maps, you can use flatMap() to merge their entries into one stream:
Map<String, String> preFilteredKafkaRecords = kafkaRecordMap.entrySet().stream()
.map(item -> getUrls(item.getKey(), item.getValue()))
.map(Map::entrySet)
.flatMap(Set::stream)
.filter(THING_1)
.filter(THING_2)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Alternatively, you can use a custom collector to fold each result into a single map:
Map<String, String> preFilteredKafkaRecords = kafkaRecordMap.entrySet().stream()
.map(item -> getUrls(item.getKey(), item.getValue()))
.filter(THING_1)
.filter(THING_2)
.collect(HashMap::new, Map::putAll, Map::putAll);

How to get the key from a map associated with another list

I need to create a map by associating List< String> with the key of the Map< String, List< String>> as follows.
Input Map< String,List< String>> - {"Fruit" -> ["apple","orange"], "Animal" -> ["cat","dog"]}
Input List< String> - {"apple","dog","xyzzy"}
output map as {"Fruit" -> "apple","Animal" -> "dog"} by discarding unmatching entries like "xyzzy" in this case"
```
Map <String, String> outputMap = new HashMap<>();
Map <String, String> tempMap = new HashMap<>();
for (Map.Entry<String, List<String>> entry : inputMap.entrySet()) {
entry.getValue().forEach(value -> tempMap.put(value, entry.getKey()));
}
inputList.forEach(value ->
{
if(tempMap.get(value)!=null)
outputMap.put(tempMap.get(value),value); });
}
The above code fails if the List contains multiple values belongs to same key because of duplication. For example
Input Map< String,List< String>> - {"Fruit" -> ["apple","orange"], "Animal" -> ["cat","dog"]}
Input List< String> - {"apple","dog","cat"}
output map as {"Fruit" -> "apple","Animal" -> "cat"} ("dog" is overriden by "cat")
Is there a way to get output as
{"Fruit" -> "apple","Animal" -> "dog", "Animal" -> "cat"}
Is there a way to achieve this in a neater and precise way?
Since your output type is Map<String, List<String>> you cannot get the output
{"Fruit" -> "apple","Animal" -> "dog", "Animal" -> "cat"}
(the String "Animal" is key and cannot be duplicated)
But, if you want filter the input map checking the containing lists you can do:
final Map<String, List<String>> inputMap = new HashMap<>();
inputMap.put("Fruit", asList("apple", "orange"));
inputMap.put("Animal", asList("cat", "dog"));
final List<String> list = asList("apple", "dog", "cat");
final Map<String, List<String>> outputMap = inputMap.entrySet().stream()
.filter(e -> e.getValue().stream().anyMatch(list::contains))
.collect(toMap(e -> e.getKey(), e -> e.getValue().stream().filter(list::contains).collect(toList())));
outputMap.forEach((k, v) -> System.out.printf("%s: %s%n", k, v));
With output:
Fruit: [apple]
Animal: [cat, dog]
The double filtering traverse lists twice, this will be better or worse depending on how the input map is. Anyway, re-grouping is not required; if the inputs will match into a high percentage, you can do:
final Map<String, List<String>> outputMap = inputMap.entrySet().stream()
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), e.getValue().stream().filter(list::contains).collect(toList())))
.filter(e -> !e.getValue().isEmpty())
.collect(toMap(e -> e.getKey(), e -> e.getValue()));
A not directly related issue is that, to improve performance, the filter list is better to define as a Set<String>.

Collect stream of EntrySet to LinkedHashMap

I want to collect the stream to a LinkedHashMap<String, Object>.
I have a JSON resource that is stored in LinkedHashMap<String, Object> resources.
Then I filter out JSON elements by streaming the EntrySet of this map.
Currently I am collecting the elements of stream to a regular HashMap. But after this I am adding other elements to the map. I want these elements to be in the inserted order.
final List<String> keys = Arrays.asList("status", "createdDate");
Map<String, Object> result = resources.entrySet()
.stream()
.filter(e -> keys.contains(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
result.put("date", "someDate");
return result;
That is why I want to collect the stream to a LinkedHashMap<String, Object>. How can I achieve this?
You can do this with Stream:
Map<String, Object> result = resources.entrySet()
.stream()
.filter(e -> keys.contains(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (x, y) -> y, LinkedHashMap::new));
The part (x, y) -> y is because of mergeFunction when find duplicate keys, it returns value of second key which found. the forth part is mapFactory which a supplier providing a new empty Map into which the results will be inserted.
An alternate way of doing this using Map.forEach is:
Map<String, Object> result = new LinkedHashMap<>();
resources.forEach((key, value) -> {
if (keys.contains(key)) {
result.put(key, value);
}
});
result.put("date", "someDate");
and if you could consider iterating on the keySet as an option:
Map<String, Object> result = new LinkedHashMap<>();
resources.keySet().stream()
.filter(keys::contains)
.forEach(key -> result.put(key,resources.get(key)));
result.put("date", "someDate");

Convert type X to Y in Map<K, Map<V, X>> using Java Stream API

I want to convert inner map from map of maps.
Old map: Map<String, Map<LocalDate, Integer>> Integer means seconds
New map: Map<String, Map<LocalDate, Duration>>
I have tried created new inner map, but got an error
Error: java: no suitable method found for putAll(java.util.stream.Stream<java.lang.Object>)
method java.util.Map.putAll(java.util.Map<? extends java.time.LocalDate,? extends java.time.Duration>) is not applicable
oldMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e -> new HashMap<LocalDate, Duration>() {{
putAll(
e.getValue().entrySet().stream()
.map(x -> new HashMap.SimpleEntry<LocalDate, Duration>
(x.getKey(), Duration.ofSeconds(x.getValue())))
);
}}
));
If you want compact code, you may use
Map<String, Map<LocalDate, Duration>> newMap = new HashMap<>();
oldMap.forEach((s,o) -> o.forEach((d, i) ->
newMap.computeIfAbsent(s, x->new HashMap<>()).put(d, Duration.ofSeconds(i))));
If you want to avoid unnecessary hash operations, you may expand it a bit
Map<String, Map<LocalDate, Duration>> newMap = new HashMap<>();
oldMap.forEach((s,o) -> {
Map<LocalDate, Duration> n = new HashMap<>();
newMap.put(s, n);
o.forEach((d, i) -> n.put(d, Duration.ofSeconds(i)));
});
Quick and clean
HashMap<String, HashMap<LocalDate, Duration>> newMap = new HashMap<>();
oldHashMap.forEach((key, innerMap) -> {
HashMap<LocalDate, Duration> newStuff = new HashMap<>();
innerMap.forEach((k2,v2) -> newStuff.put(k2,Duration.ofSeconds(v2)));
newMap.put(key, newStuff);
});
And one more...
Map<String, Map<LocalDate, Duration>> newMap = map.entrySet().stream().collect(
Collectors.toMap(Entry::getKey,
entry -> entry.getValue().entrySet().stream().collect(
Collectors.toMap(Entry::getKey, e -> Duration.ofSeconds(e.getValue())))));
My two cents, create a method to transform Map<K, V1> to a Map<K, V2>:
public static <K,V1,V2> Map<K, V2> transformValues(final Map<K, V1> input, final Function<V1,V2> transform) {
Function<Map.Entry<K, V1>, V2> mapper = transform.compose(Map.Entry::getValue);
return input.entrySet().stream()
.collect(toMap(Map.Entry::getKey, mapper));
}
Then your code becomes:
Map<String, Map<LocalDate, Duration>> transformed
= transformValues(maps, map -> transformValues(map, Duration::ofSeconds));
Let's extract a helper method toMap to makes the problem more simpler.
Map<String, Map<LocalDate, Duration>> result = oldMap.entrySet().stream()
.map(entry -> new AbstractMap.SimpleEntry<>(
entry.getKey(),
entry.getValue().entrySet().stream()
.collect(toMap(it -> Duration.ofSeconds(it.longValue())))
// ^--- mapping Integer to Duration
)).collect(toMap(Function.identity()));
<K, V, R> Collector<Map.Entry<K, V>, ?, Map<K, R>> toMap(Function<V, R> mapping) {
return Collectors.toMap(
Map.Entry::getKey,
mapping.compose(Map.Entry::getValue)
);
}
AND you can simplified the code as further since the primary logic is duplicated: adapts a value to another value in a Map.
Function<
Map<String, Map<LocalDate, Integer>>,
Map<String, Map<LocalDate, Duration>>
> mapping = mapping(mapping(seconds -> Duration.ofSeconds(seconds.longValue())));
// | |
// | adapts Map<LocalDate, Integer> to Map<LocalDate,Duration>
// |
//adapts Map<String,Map<LocalDate,Integer>> to Map<String,Map<LocalDate,Duration>>
Map<String, Map<LocalDate, Duration>> result = mapping.apply(oldMap);
<K, V1, V2> Function<Map<K, V1>, Map<K, V2>> mapping(Function<V1, V2> mapping) {
return it -> it.entrySet().stream().collect(toMap(mapping));
}
THEN you can use it anywhere that needs a Function<Map<?,?>,Map<?,?>> to adapts the value to another value,for example:
Map<String, Map<LocalDate, Duration>> result = Optional.of(oldMap)
.map(mapping(mapping(seconds -> Duration.ofSeconds(seconds.longValue()))))
.get();
If i understand you correct,you can see following code:
oldMap.entrySet().stream()
.collect(Collectors.toMap(x -> x.getKey(),
x -> {
Map<LocalDate, Duration> temp = new HashMap<>();
x.getValue().forEach((k, v) -> {
temp.put(k, Duration.ofSeconds(v));
});
return temp;
})
);
For completeness, here's a version that uses Guava's Maps.transformValues:
Map<String, Map<LocalDate, Duration>> result =
Maps.transformValues(oldMap, m -> Maps.transformValues(m, Duration::ofSeconds));
I would prefer to iterate over the entries of your old map and streaming over the inner map:
for (Entry<String, Map<LocalDate, Integer>> entry : oldMap.entrySet()) {
Map<LocalDate, Duration> asDuration = entry.getValue().entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(), e -> Duration.ofSeconds(e.getValue().longValue())));
newMap.put(entry.getKey(), asDuration);
}
Otherwise you need a second stream inside your collect:
newMap = oldMap.entrySet().stream()
.collect(Collectors.toMap(s -> s.getKey(), s -> s.getValue().entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(), e -> Duration.ofSeconds(e.getValue().longValue())))));
Here is solution by StreamEx:
EntryStream.of(oldMap)
.mapValues(v -> EntryStream.of(v).mapValues(Duration::ofSeconds).toMap())
.toMap();

Merging map and modifying value

There are two maps and I am trying to merge them into a single map (finalResp).
Map<String, String[]> map1 = new HashMap<>();
Map<String, String> map2 = new HashMap<>();
HashMap<String, String> finalResp = new HashMap<String, String>();
Solution - pre Java 8 - achieved like below:
for (Map.Entry<String, String[]> entry : map1.entrySet()) {
if (map2.containsKey(entry.getKey())) {
String newValue = changetoAnother(map1.get(entry.getKey()), map2.get(entry.getKey()));
finalResp.put(entry.getKey(), newValue);
}
}
Using Java 8, I am stuck at this:
HashMap<String, String> map3 = new HashMap<>(map2);
map1.forEach((k, v) -> map3.merge(k, v, (i, j) -> mergeValue(i, j) ));
How can I check if a map 2 key is not present in map 1 and modify the values?
One possible way is to filter the unwanted elements (not contained in map2) and collect the result into a new Map:
Map<String, String> finalResp =
map1.entrySet().stream().filter(e -> map2.containsKey(e.getKey()))
.collect(Collectors.toMap(
Entry::getKey,
e -> changetoAnother(e.getValue(), map2.get(e.getKey()))
));
Another way would be to create a copy of map2, retain all the keys of this Map that are also contained in map1 keys and finally replace all the values by applying the function changetoAnother.
Map<String, String> result = new HashMap<>(map2);
result.keySet().retainAll(map1.keySet());
result.replaceAll((k, v) -> changetoAnother(map1.get(k), v));
Note that the advantage of the first solution is that it can be easily generalized to work for any two Maps:
private <K, V, V1, V2> Map<K, V> merge(Map<K, V1> map1, Map<K, V2> map2, BiFunction<V1, V2, V> mergeFunction) {
return map1.entrySet().stream()
.filter(e -> map2.containsKey(e.getKey()))
.collect(Collectors.toMap(
Entry::getKey,
e -> mergeFunction.apply(e.getValue(), map2.get(e.getKey()))
));
}
with
Map<String, String> finalResp = merge(map1, map2, (v1, v2) -> changetoAnother(v1, v2));

Categories