I would like to know if someone have an easy way to merge 2 deep nested maps together ?
For instance, I would like to get :
[
"a" : "1",
"animals" : ["cat" : "blue"]
] + [
"b" : 2,
"animals" : ["dog" : "red"]
] == [
"a" : 1,
"b" : 2,
"animals" : [
"cat" : "blue",
"dog" : "red"]
]
There is someone having easy solution ?
You can write one for Map using recursion:
Map.metaClass.addNested = { Map rhs ->
def lhs = delegate
rhs.each { k, v -> lhs[k] = lhs[k] in Map ? lhs[k].addNested(v) : v }
lhs
}
def map1 = [
"a" : "1",
"animals" : ["cat" : "blue"]
]
def map2 = [
"b" : 2,
"animals" : ["dog" : "red"]
]
assert map1.addNested( map2 ) == [
a: '1',
animals: [cat: 'blue', dog: 'red'],
b: 2
]
I had a similar solution as #dmahapatro, but with a method with variable arguments:
def m1 = [a: 1, animals: [cat: 'blue']]
def m2 = [b: 2, animals: [dog: 'red']]
Map merge(Map... maps) {
Map result
if (maps.length == 0) {
result = [:]
} else if (maps.length == 1) {
result = maps[0]
} else {
result = [:]
maps.each { map ->
map.each { k, v ->
result[k] = result[k] instanceof Map ? merge(result[k], v) : v
}
}
}
result
}
assert [:] == merge()
assert m1 == merge(m1)
assert [a:1, b:2, animals:[cat:'blue', dog:'red']] == merge(m1, m2)
This is a very short, concise way of doing it without using the meta-programming:
Map one = ["a" : "1", "animals" : ["cat" : "blue"] ]
Map two = ["b" : "2", "animals" : ["dog" : "red"] ]
Map three = [:]
(one.entrySet() + two.entrySet()).each { entry ->
three[entry.key] = three.containsKey(entry.key) ? [:] << three[entry.key] << entry.value : entry.value
}
println three
Renders the output:
[a:1, animals:[cat:blue, dog:red], b:2]
Another solution similar to #dmahapatro, but without using metaClass:
Map mergeMaps(Map lhs, Map rhs) {
rhs.each { k, v ->
lhs[k] = (lhs[k] in Map ? mergeMaps(lhs[k], v) : v)
}
return lhs
}
def m1 = [a: 1, animals: [cat: 'blue']]
def m2 = [b: 2, animals: [dog: 'red']]
mergeMaps(m1, m2)
println(m1)
Related
I have a class Obj like :
public class Obj{
private String size
private String shape
private String name
}
In one of my other classes I have a Map<String,List<Obj>>.
For each String in my Map, I want to regroup every Obj from my List where shape and/or size are equals, based on the String of my Map.
For now I have something like :
functionName(Map<String, List<Obj>> objMap){
objMap.foreach((type,objs) -> {
switch(type){
case "type_1":
// regroup objs by shape
// then do some treatment
case "type_2":
// regroup objs by size
// then do some treatment
case "type_3":
// regroup objs by shape & size
// then do some treatment
}
}
}
From here I'm kinda stuck on how to get a List<List<Obj>> from my List<Obj>.
Any idea would help me, even if not very optimized.
Values and expected result :
For example I have a Map as :
{
"key" :"type_1",
"value" : [
{
"size" : "m1",
"shape" : "s1",
"name":"Obj_1"
},
{
"size" : "m2",
"shape" : "s1",
"name":"Obj_2"
},
{
"size" : "m3",
"shape" : "s2",
"name":"Obj_3"
}
]
}
Then in my function I should be in case "type_1" so i'll group them by shape, return me something like :
[
[
{
"size" : "m1",
"shape" : "s1",
"name":"Obj_1"
},
{
"size" : "m2",
"shape" : "s1",
"name":"Obj_2"
}
],
[
{
"size" : "m3",
"shape" : "s2",
"name":"Obj_3"
}
]
]
But if my key was type_3, I should have :
[
[
{
"size" : "m1",
"shape" : "s1",
"name":"Obj_1"
}
],
[
{
"size" : "m2",
"shape" : "s1",
"name":"Obj_2"
}
],
[
{
"size" : "m3",
"shape" : "s2",
"name":"Obj_3"
}
]
]
because none of them have the same shape and size
If my Map was :
{
"key" :"type_3",
"value" : [
{
"size" : "m1",
"shape" : "s1",
"name":"Obj_1"
},
{
"size" : "m2",
"shape" : "s1",
"name":"Obj_2"
},
{
"size" : "m1",
"shape" : "s1",
"name":"Obj_3"
}
]
}
then I should get :
[
[
{
"size" : "m1",
"shape" : "s1",
"name":"Obj_1"
},
{
"size" : "m1",
"shape" : "s1",
"name":"Obj_3"
}
],
[
{
"size" : "m2",
"shape" : "s1",
"name":"Obj_2"
}
]
]
Case for shape (but you get the idea):
List<List<Obj>> regroup(List<Obj> objs) {
return new ArrayList<>(
objs.stream()
.collect(Collectors.groupingBy(Obj::getShape))
.values()
);
}
Define a List<List<Obj>> finalList. Keep a map to trace the index of the list for certain type in the finalList.
Map<String, Integer> indexMap;
Whenever you get a object of type1, try to get list like this
List<Obj> l1 = indexMap.getOrElse(new ArrayList());
Then append the object to that list and put back in index of the final list.
List<List<Obj>> finalList = new ArrayList<>();
Map<String, Integer> indexMap = new HashMap<>();
functionName(Map<String, List<Obj>> objMap){
objMap.foreach((type,objs) -> {
switch(type){
case "type_1":
Integer index = indexMap.getOrDefault(type_1, -1);
List<Obj> list = index != -1 ? finalList.get(index) : new ArrayList<Obj>();
list.add(obj);
finalList.add(index, list); // do this for other types
// regroup objs by shape
// then do some treatment
case "type_2":
// regroup objs by size
// then do some treatment
case "type_3":
// regroup objs by shape & size
// then do some treatment
}
}
}
You should try to create nested Map<String, Map<String, List<Obj>>> to store values after grouping.
For example:
Map<String, Map<String, List<Obj>>> results = new HashMap<>();
Although, I would create another class for grouped records to make the code more readable:
class GroupedObj{
Map<String, List<Obj>> records = new HashMap<>();
}
Map<String, GroupedObj> results = new HashMap<>();
You should be able to use the groupingBy collector:
Map<String, List<Obj>> yourMap = ...;
List<List<Obj>> byShape = new ArrayList<>(yourMap.values().stream()
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(Obj::getShape))
.values());
As a method:
static <T> List<List<Obj>> partition(
final Map<String, List<Obj>> map,
final Function<? super Obj, ? extends T> key) {
return new ArrayList<>(map.values().stream() // Stream<List<Obj>>
.flatMap(Collection::stream) // Stream<Obj>
.collect(Collectors.groupingBy(key)) // Map<T, List<Obj>>
.values() // Collection<List<Obj>>
); // List<List<Obj>>
}
Calling the method:
final Map<String, List<Obj>> yourMap = ...;
final List<List<Obj>> shapes = partition(yourMap, Obj::getShape);
If you have extracted the list only and do not need to process a map – in that case the "map"-part is irrelevant for the question and should/could be removed – the method looks almost identical (the classic "left as an exercise to the reader", but here it goes):
static <T> List<List<Obj>> partition(
final List<Obj> list,
final Function<? super Obj, ? extends T> key) {
return new ArrayList<>(list.stream() // Stream<Obj>
.collect(Collectors.groupingBy(key)) // Map<T, List<Obj>>
.values() // Collection<List<Obj>>
); // List<List<Obj>>
}
Usage:
final List<Obj> yourList = ...;
final List<List<Obj>> shapes = partition(yourList, Obj::getShape);
Let's say I have the following list.
List<StringInteger> test = new ArrayList<>(); //StringInteger is just a pojo with String and int
test.add(new StringInteger("a", 1));
test.add(new StringInteger("b", 1));
test.add(new StringInteger("a", 3));
test.add(new StringInteger("c", 1));
test.add(new StringInteger("a", 1));
test.add(new StringInteger("c", -1));
System.out.println(test); // [{ a : 1 }, { b : 1 }, { a : 3 }, { c : 1 }, { a : 1 }, { c : -1 }]
I need to write a method that would unite items by String key and add integers. So that the result list would be [{ a : 5 }, { b : 1 }, { c : 0 }]
I could do it using HashMap, but if I go that way - I'll have to create a Map, then use enhanced for-loop with if(containsKey(...)) and then convert it back to List. It just seems like an overkill.
Is there a more elegant solution? I thought that flatMap from Stream API should do the thing, but I cannot figure out how.
Here's my clumsy solution. It works, but I believe that it can be done more simple than that.
Map<String, Integer> map = new HashMap<>();
for (StringInteger stringInteger : test) {
if (map.containsKey(stringInteger.getKey())) {
int previousValue = map.get(stringInteger.getKey());
map.put(stringInteger.getKey(), previousValue + stringInteger.getValue());
} else {
map.put(stringInteger.getKey(), stringInteger.getValue());
}
}
List<StringInteger> result = map.entrySet()
.stream()
.map(stringIntegerEntry -> new StringInteger(stringIntegerEntry.getKey(), stringIntegerEntry.getValue()))
.collect(Collectors.toList());
System.out.println(result); // [{ a : 5 }, { b : 1 }, { c : 0 }]
The simplest way to accomplish this is likely
List<StringInteger> combined = test.stream()
.collect(
Collectors.groupingBy(
StringInteger::getKey,
Collectors.summingInt(StringInteger::getValue)))
.entrySet()
.stream()
.map(entry -> new StringInteger(entry.getKey(), entry.getValue()))
.toList();
Here is a full example with code based on the code seen in two good Answers by Ivanchenko and by Wasserman.
Here, we use a record in Java 16+ to define your StringInt class.
The name StringInt is used rather than StringInteger, to stress that we have a primitive int as the member field type rather than Integer class as the type.
I should think a Map < String , Integer > would suffice for your goal.
record StringInt( String string , int integer ) { }
List < StringInt > inputs =
List.of(
new StringInt( "a" , 1 ) ,
new StringInt( "b" , 1 ) ,
new StringInt( "a" , 3 ) ,
new StringInt( "c" , 1 ) ,
new StringInt( "a" , 1 ) ,
new StringInt( "c" , - 1 )
);
Map < String, Integer > results =
inputs
.stream()
.collect(
Collectors.groupingBy(
StringInt :: string , // Key
Collectors.summingInt( StringInt :: integer ) // Value
) );
results = {a=5, b=1, c=0}
Or, if you insist on instantiating StringInt objects as the result:
record StringInt( String string , int integer ) { }
List < StringInt > inputs =
List.of(
new StringInt( "a" , 1 ) ,
new StringInt( "b" , 1 ) ,
new StringInt( "a" , 3 ) ,
new StringInt( "c" , 1 ) ,
new StringInt( "a" , 1 ) ,
new StringInt( "c" , - 1 )
);
List < StringInt > results =
inputs
.stream()
.collect(
Collectors.groupingBy(
StringInt :: string , // Key
Collectors.summingInt( StringInt :: integer ) // Value
) )
.entrySet() // Returns a Set < Entry < String , Integer > >
.stream()
.map(
entry -> new StringInt( entry.getKey() , entry.getValue() )
)
.toList();
As #LouisWasserman said in the comment, HashMap is the right tool for this task.
To translate the whole code into a stream, you can use the built-in collector groupingBy() in conjunction with summingInt as the downstream collector grouping.
result = test.stream()
.collect(Collectors.groupingBy( // creates an intermediate map Map<String, Integer>
StringInteger::getKey, // mapping a key
Collectors.summingInt(StringInteger::getValue) // generating a value
))
.entrySet().stream()
.map(entry -> new StringInteger(entry.getKey(), entry.getValue()))
.toList();
Here's my clumsy solution. It works, but I believe that it can be done more simple than that.
Just to show you, you can do the adding up in the map more neatly, without using streams:
for (StringInteger stringInteger : test) {
map.merge(stringInteger.getKey(), stringInteger.getValue(), Integer::sum);
}
I have a data structure like below. I'm trying to group the objects in such a way like Map<String, List<String>> where key is the entryId and value is the List of groups it belongs to. entryId is always unique inside a group.
Example: entryId "1111" belongs to group1,group2,group3. I'm using the old java 7 way to iterate through the lists and checking. Is there any best possible way using Java8 Collectors/grouping to achieve this.
List<Group> where each Group object will have a list of Entry objects.
[
{
"id":"group1",
"entries":[
{
"entryId":"1111",
"name":"test1"
},
{
"entryId":"2222",
"name":"test2"
},
{
"entryId":"3333",
"name":"test3"
}
]
},
{
"id":"group2",
"entries":[
{
"entryId":"4444",
"name":"test1"
},
{
"entryId":"1111",
"name":"test2"
},
{
"entryId":"2222",
"name":"test3"
}
]
},
{
"id":"group3",
"entries":[
{
"entryId":"1111",
"name":"test1"
},
{
"entryId":"5555",
"name":"test2"
},
{
"entryId":"3333",
"name":"test3"
}
]
}
]
So the expected out put is this :
[
{
"1111":[
"group1",
"group2",
"group3"
]
},
{
"2222":[
"group1",
"group2"
]
},
{
"3333":[
"group1",
"group3"
]
},
{
"4444":[
"group2"
]
},
{
"5555":[
"group3"
]
}
]
I'm using below way currently. which is working as expected, but is there a much simpler way in Java 8 I can achieve this.
public Map<String, List<String>> mapEntries(List<Group> groups) {
Map<String, List<String>> entryMaps = new HashMap<>();
for (Group group : groups) {
for (Entry entry : group.getEntries()) {
List<String> groupsEntryBelongs = new ArrayList<>();
if (groups.iterator().hasNext() && !entryMaps.keySet().contains(entry.getEntryId())) {
updateGroups(groups, entry.getEntryId(), groupsEntryBelongs, entryMaps);
}
}
}
return entryMaps;
}
void updateGroups(List<Group> groups, String id, List<String> groupsEntryBelongs, Map<String, List<String>> entryMaps) {
for (Group group : groups) {
for (Entry entry : group.getEntries()) {
if (entry.getEntryId().equalsIgnoreCase(id)) {
groupsEntryBelongs.add(group.getId());
}
}
}
entryMaps.put(id, groupsEntryBelongs);
}
You can do it as follows:
Map<String, Set<String>> entryMaps = new LinkedHashMap<>();
groups.forEach(group ->
group.getEntries().forEach(entry ->
entryMaps.computeIfAbsent(
entry.getEntryId().toLowerCase(),
k -> new LinkedHashSet<>())
.add(group.getId())));
This iterates the groups, then each group's entries and uses Map.computeIfAbsent to put an entry with a new, empty LinkedHashSet if the key wasn't present, returning either this empty set or the one matching that key. Then, the group id is added to this returned set.
Note: I'm using a Set instead of a List for values, to avoid possible duplicates. And LinkedHashMap and LinkedhashSet guarantee insertion-order.
Something like this ought to work, it requires making some sort of intermediate tuple object:
groups.stream()
.flatMap(group -> group.getEntries().stream()
.map(entry -> Map.entry(entry.getEntryId(), group.getId())))
.collect(Colectors.groupingBy(Map.Entry::getKey,
Colectors.mapping(Map.Entry::getValue, toList())));
List<List<EmployeeHourLogDetails>> resultLisSalesman = new ArrayList<>
(employeeHourLogHeader.getEmployeeHourLogDetails().stream()
.collect(Collectors.groupingBy(d ->
d.getEmployeeId())).values());
I have an example written in Java that I would like to convert into Swift. Below is a section of the code. I would really appreciate if you can help.
Map<String, Integer> someProtocol = new HashMap<>();
someProtocol.put("one", Integer.valueOf(1));
someProtocol.put("two", Integer.valueOf(2));
for (Map.Entry<String, Integer> e : someProtocol.entrySet() {
int index = e.getValue();
...
}
NOTE: entrySet() is a method of the java.util.Map interface whereas getValue() is a method of the java.util.Map.Entry interface.
I believe you can use a dictionary. Here are two ways to do the dictionary part.
var someProtocol = [String : Int]()
someProtocol["one"] = 1
someProtocol["two"] = 2
or try this which uses type inference
var someProtocol = [
"one" : 1,
"two" : 2
]
as for the for loop
var index: Int
for (e, value) in someProtocol {
index = value
}
let stringIntMapping = [
"one": 1,
"two": 2,
]
for (word, integer) in stringIntMapping {
//...
print(word, integer)
}
I guess it will be something like that:
let someProtocol = [
"one" : 1,
"two" : 2
]
for (key, value) in someProtocol {
var index = value
}
This is about Java, but for readability's sake, I'm going to write the examples down in JSON.
Say I have a List of Maps set up like this:
[{
"id": 1,
"foo": 12,
"bar": 34
}, {
"id": 1,
"baz": 56
}, {
"id": 2,
"foo": 78
}, {
"id": 2,
"bar": 90
}]
What I'd like to do is merge maps that have the same id. Basically, I want to end up with something like this:
[{
"id": 1,
"foo": 12,
"bar": 34,
"baz": 56
}, {
"id": 2,
"foo": 78,
"bar": 90
}]
All other questions I found deal with merging maps in unrelated ways, and most are only concerned about two maps, not a variable amount.
This code seems to work, but strikes me as a little verbose:
List<Map<String, Integer>> toRemove = new ArrayList<Map<String, Integer>>();
for (Map<String, Integer> map : list) {
if (toRemove.contains(map)) {
continue;
}
int id = map.get("id");
for (Map<String, Integer> otherMap : list) {
if (map.equals(otherMap)) {
continue;
}
int otherId = otherMap.get("id");
if (id == otherId) {
map.putAll(otherMap);
toRemove.add(otherMap);
}
}
}
list.removeAll(toRemove);
Is there a more elegant way to achieve this?
I would organize the result in a Map of Maps
Map<Integer, Map<String,Integer>> mapOfMaps = new HashMap<Integer, Map<String,Integer>>();
for(Map<String,Integer> map : list){
Integer id = map.get("id");
Map<String,Integer> existingMap = mapOfMaps.get(id);
if(existingMap == null){
mapOfMaps.put(id, map);
}else{
existingMap.putAll(map);
}
}
As I commented above: this in the case you don't need to sum values (apart from id maps do not share other keys, or if they do values would be replaced)
Just make a groupMap that is a new Map, with id is the key and element map is value:
List<Map<String, Integer>> toRemove = new ArrayList<Map<String, Integer>>();
Map<Integer, Map<String, Integer>> groupMap = new HashMap<>();
for (Map<String, Integer> m : toRemove) {
Integer id = m.get("id");
Map<String, Integer> tmp = groupMap.get(id);
if (tmp == null) {
groupMap.put(id, m);
} else {
tmp.putAll(m);
}
}
List<Map<String, Integer>> newList = new ArrayList<>(groupMap.values());
Then, the newList is your result now.
My сrazy solution with streams:
List<Map<String, Integer>> result = list.stream()
.collect(Collectors.groupingBy(m -> m.get("id")))
.values().stream()
.map(m -> m.stream().<Map<String, Integer>>collect(HashMap::new, Map::putAll, Map::putAll))
.collect(Collectors.toList());