Merge two Maps (Map<String, Map<String, Object>>) with duplicate keys - java

I have two ImmutableMaps from guava that I am trying to combine them where they can have duplicate keys;
Map<String, Map<String, Object>> map1 = new ImmutableMap.Builder<String, Map<String, Object>>()
.put("config", ImmutableMap.of(
"key1", "value1",
"key2", "value2"))
.put("categoryapproval", ImmutableMap.of("reason_code", "listing_quality"))
.build();
Map<String, Map<String, Object>> map2 = new ImmutableMap.Builder<String, Map<String, Object>>()
.put("config", ImmutableMap.of(
"key1", "value3",
"key4", "value4"))
.build();
So, I can not use putAll() method because it throws DuplicateKeyException which is as expected. The output I am trying to get is like;
"config" --> "key1": {value1, value3},
"key2": {value2},
"key4": {value4}
Finally, I have also tried MultiValueMap, however, MultiValueMap keeps values as List where I need to iterate over. In map1 I can get value1 by map1.get("config").get("key1") where value1 can be any kind of object. I appreciate for any kind of help.

You can use Guava's Multimap and Java 8's Map.merge(K, V, BiFunction):
Map<String, Multimap<String, Object>> merged = new HashMap<>();
BiFunction<Multimap<String, Object>, Multimap<String, Object>, Multimap<String, Object>> remappingFunction = (value1, value2) -> {
Multimap<String, Object> multimap = HashMultimap.<String, Object>create();
multimap.putAll(value1);
multimap.putAll(value2);
return multimap;
};
map1.forEach((key, value) -> merged.merge(key, Multimaps.forMap(value), remappingFunction));
map2.forEach((key, value) -> merged.merge(key, Multimaps.forMap(value), remappingFunction));
merged.get("config").get("key1");
If you are not using Java 8 then you'll need to manage the merging of multimaps in some other way but the idea is the same.

Related

Convert List of Maps to a single Map with VAVR

I'm struggling to reduce a List of Maps to a single Map using Vavr
<List<Map<String, Object>>>
to
<Map<String, Object>>
I tried it with flatmap/reduce/merge but unfortunately with no happy end :-(
Can someone provided an example how to do it with io.vavr.collection.List/io.vavr.collection.Map ?
You have several options.
For instance, given the following data set (sorry for its simplicity):
Map<String, Object> map1 = HashMap.of("key1", "value1", "key2", "value2");
Map<String, Object> map2 = HashMap.of("key3", "value3", "key4", "value4");
Map<String, Object> map3 = HashMap.of("key5", "value5", "key6", "value6");
List<Map<String, Object>> list = List.of(map1, map2, map3);
You can combine the different Maps with fold, for instance:
Map<String, Object> result = list.fold(HashMap.empty(), (m1, m2) -> m1.merge(m2));
You can use reduce as well:
Map<String, Object> result = list.reduce((m1, m2) -> m1.merge(m2));
Both of them use the Map merge method. There is an overloaded version of this method that allows you to define how collisions should be resolved.
I have no idea what vavr has to do with this.
list.stream() // A
.flatMap(map -> map.entrySet().stream()) // B
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
[A] Now you have a stream of Map<String, Object>.
[B] Now you have a stream of map entries.
Now you have a stream of key/value pairs.
Then just collect them into a map by providing a function to extract a key from a Map.Entry object, and a function that extracts a value.
Note that if you have a map of type Map<String, Object>, I give it 99% odds you're doing something wrong. Java is nominally, statically, and strongly typed, after all.

Efficient way to iterate and copy the values of HashMap

I want to convert:
Map<String, Map<String, List<Map<String, String>>>> inputMap
to:
Map<String, Map<String, CustomObject>> customMap
inputMap is provided in the config and is ready but I need to customMap Format. CustomObject will be derived from List<Map<String, String>> using few lines of code in a function.
I have tried a normal way of iterating input map and copying key values in customMap. Is there any efficient way of doing that using Java 8 or some other shortcut?
Map<String, Map<String, List<Map<String, String>>>> configuredMap = new HashMap<>();
Map<String, Map<String, CustomObj>> finalMap = new HashMap<>();
for (Map.Entry<String, Map<String, List<Map<String, String>>>> attributeEntry : configuredMap.entrySet()) {
Map<String, CustomObj> innerMap = new HashMap<>();
for (Map.Entry<String, List<Map<String, String>>> valueEntry : attributeEntry.getValue().entrySet()) {
innerMap.put(valueEntry.getKey(), getCustomeObj(valueEntry.getValue()));
}
finalMap.put(attributeEntry.getKey(), innerMap);
}
private CustomObj getCustomeObj(List<Map<String, String>> list) {
return new CustomObj();
}
One solution is to stream the entrySet of inputMap, and then use Collectors#toMap twice (once for the outer Map, and once for the inner Map):
Map<String, Map<String, CustomObj>> customMap = inputMap.entrySet()
.stream()
.collect(Collectors.toMap(Function.identity(), entry -> {
return entry.getValue()
.entrySet()
.stream()
.collect(Collectors.toMap(Function.identity(),
entry -> getCustomeObj(entry.getValue())));
}));
You could stream, but that ain't going to look readable; at least to me. So if you have a method:
static CustomObject fun(List<Map<String, String>> in) {
return .... // whatever processing you have here
}
you could still use the java-8 syntax, but in a different form:
Map<String, Map<String, CustomObject>> customMap = new HashMap<>();
inputMap.forEach((key, value) -> {
value.forEach((innerKey, listOfMaps) -> {
Map<String, CustomObject> innerMap = new HashMap<>();
innerMap.put(innerKey, fun(listOfMaps));
customMap.put(key, innerMap);
});
});
If you can make the inner map immutable, you could make that even shorter:
inputMap.forEach((key, value) -> {
value.forEach((innerKey, listOfMaps) -> {
customMap.put(key, Collections.singletonMap(innerKey, fun(listOfMaps)));
});
});
IMHO streaming is not so bad idea. There're no bad tools. It depends on how you're using them.
In this particular case I would extract the repeating pattern into an utility method:
public static <K, V1, V2> Map<K, V2> transformValues(Map<K, V1> map, Function<V1, V2> transformer) {
return map.entrySet()
.stream()
.collect(toMap(Entry::getKey, e -> transformer.apply(e.getValue())));
}
The method above can be implemented using any approach, though I think Stream API fits pretty well here.
Once you defined the utility method, it can be used as simple as follows:
Map<String, Map<String, CustomObj>> customMap =
transformValues(inputMap, attr -> transformValues(attr, this::getCustomObj));
The actual transformation is effectively one liner. So with proper JavaDoc for transformValues method the result code is pretty readable and maintainable.
How about Collectors.toMap for the entries both at an outer and inner level such as:
Map<String, Map<String, CustomObj>> finalMap = configuredMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
attributeEntry -> attributeEntry.getValue().entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
valueEntry -> getCustomeObj(valueEntry.getValue())))));

Java8 - How to convert a nested maps to list of nested maps gathered by value of inner map's key

I have a nested maps object like below
{12345={{"status":"200","outcome":"Success","message":"Account created"}}
{23121={{"status":"400","outcome":"Exception","message":"Invalid State value"}}
{43563={{"status":"200","outcome":"Success","message":"Account updated"}}
{72493={{"status":"400","outcome":"Exception","message":"Bad Request"}}
I need to transform this into Map<String, List<Map<String, Map<String, String>>> where the key of outer map is value of the status ( 200 or 400 ) and the value is the list of original maps that have status = 200 or 400 or other valid values from the inner map.
So, the new structure would look like
{{200={[{12345={"status":"200","outcome":"Success","message":"Account created"}},
{43563={"status":"200","outcome":"Success","message":"Account updated"}}
]
},
{400={[{23121={"status":"400","outcome":"Exception","message":"Invalid State value"}},
{72493={"status":"400","outcome":"Exception","message":"Bad Request"}}
]
}
}
Ultimately, I need to generate a report based on the different stati.
This is what I have started with, but am stuck.
I want to loop through outer map, get the inner map, get the value of status key and add the map to a list based on status code value.
This is how I am doing it using loops
private static Map<String, List<Map<String, Map<String, String>>>> covertToReport(Map<String, Map<String, String>> originalMap) {
Map<String, List<Map<String, Map<String, String>>>> statusBasedListOfMaps = new TreeMap<>();
//loop through the map
//for each key, get the inner map
//get the status value for each inner map
List<Map<String, Map<String, String>>> accountsMapsList;
for (Entry<String, Map<String, String>> entry : originalMap.entrySet()) {
String accNum = entry.getKey();
Map<String, String> childMap = entry.getValue();
String stausVal = childMap.get("status");
accountsMapsList = statusBasedListOfMaps.get(stausVal) == null ? new ArrayList<>() : statusBasedListOfMaps.get(stausVal);
accountsMapsList.add((Map<String, Map<String, String>>) entry);
statusBasedListOfMaps.put(stausVal, accountsMapsList);
}
return statusBasedListOfMaps;
}
Of course, the below code doesn't compile, but that is what I am trying to get.
private static void covertToReport(Map<String, Map<String, String>> originalMap) {
Map<String, List<Map<String, Map<String, String>>>> statusBasedListOfMaps;
statusBasedListOfMaps = originalMap.entrySet()
.stream()
.filter(e -> e.getValue()
.values()
.stream()
.map(innerMap -> Collectors.toList())
.collect(Collectors.toMap(Map.Entry::getKey, Collectors.toList(e)));
Is this possible?
You can just use Collectors.groupingBy() with Collectors.mapping():
private static Map<String, List<Map<String, Map<String, String>>>> convertToReport(Map<String, Map<String, String>> originalMap) {
return originalMap.entrySet().stream()
.collect(Collectors.groupingBy(e -> e.getValue().get("status"),
Collectors.mapping(Map::ofEntries, Collectors.toList())));
}
You group by status and then map the associated entry to an own map using Map.ofEntries(). If you are using Java you can use this instead of Map::ofEntries:
e -> new HashMap<>() {{ put(e.getKey(), e.getValue()); }}
The result will be this:
200=[
{12345={status=200, message=Account created, outcome=Success}},
{43563={status=200, message=Account created, outcome=Success}}
],
400=[
{72493={status=400, message=Invalid State value, outcome=Exception}},
{23121={status=400, message=Invalid State value, outcome=Exception}}
]
Your function return a Map<String, List<Map<String, Map<String, String>>>>, but your structure look like Map<String, Map<String, Map<String, String>>>
If what you really like is a Map<String, Map<String, Map<String, String>>> here is the code:
Map<String, Map<String, Map<String, String>>> result= map.entrySet().stream().collect(Collectors.groupingBy(entry -> entry.getValue().get("status"), Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));

Java 8: Transform EnumMap<ExampleEnum, String> to Map<String, Object>

I have a situation where I need to copy my EnumMap<ExampleEnum,String> to Map<String, Object>. Many examples on Stack Overflow shows how to cast from one data type to another but not from enum. I have tried doing it through stream but no luck. Here is my code
private enum Number{
One, Two, Three;
}
final Map<Number, String> map = Collections.synchronizedMap(new EnumMap<Number, String> (Number.class));
populateMap(map);
Map<String, Object> newMap= new HashMap<String, Object>();
Now I want to do something like
newMap.putAll(map);
How can I do it through Stream APIs?
A more concise answer is,
final Map<Number, String> map = Collections.synchronizedMap(new EnumMap<>(Number.class));
Map<String, Object> newMap= new HashMap<>();
map.forEach((key, value) -> newMap.put(key.name(), value));
Map<String, Object> newMap = map.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue));

How to filter, using stream, Map<String, Object> by List<String> which contains keys

I have Map<String, Object> which has to be become Map<String, String>. Filtering should be done by List<String>.
That list contains keys of map elements that should be in new map.
For this I need to use streams.
Map<String, Object> oldMap;
List<String> keysForFiltering;
Map<String, String> newMap;
It would be more efficient if the filter would operate on a Set of keys instead of a List of keys, since searching a Set is more efficient than searching a List.
Map<String, String> newMap =
oldMap.entrySet()
.stream()
.filter(e -> keysForFiltering.contains(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().toString()));
since you have a map then you can get the stream of that and use a custom predicate, that predicate need to check if the Entry.key is present in your list or not
Map<String, String> myMap = new HashMap<>();
myMap.put("A", "fortran");
myMap.put("B", "java");
myMap.put("C", "c++");
myMap.put("D", "php");
List<String> keysForFiltering = Arrays.asList("A", "C");
Predicate<Entry<String, String>> myPredicate = t -> keysForFiltering.contains(t.getKey());
Map<String, String> filteredMap = myMap
.entrySet().stream().filter(myPredicate)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
System.out.println(filteredMap);

Categories

Resources