Reputation: 1215
I have two HashMaps in the form
Map<Integer, Map<String,Double>> MAP_1
Map<Integer, Map<String,Double>> MAP_2
Let INNER be the inner map, i.e.
Map<String,Double>
For each pair of corresponding keys, I want to perform an operation (e.g. subtraction) between the two inner maps. In turn, this operation should be performed between two corresponding keys of the inner maps. The resulting map should be
Map<Integer,List<Double> RESULT
Sample maps are
MAP_1
------------------------------
| | 2016-10-02 10.0 |
| ID1 | 2016-10-03 20.0 |
| | 2016-10-04 30.0 |
------------------------------
| | 2016-10-02 1.00 |
| ID2 | 2016-10-03 2.00 |
| | 2016-10-04 3.00 |
------------------------------
MAP_2
------------------------------
| | 2016-10-02 2.00 |
| ID1 | 2016-10-03 3.00 |
| | |
------------------------------
| | 2016-10-02 1.00 |
| ID3 | 2016-10-03 2.00 |
| | 2016-10-04 3.00 |
------------------------------
RESULT
---------------
| | 8.00 |
| ID1 | 17.0 |
| | |
---------------
So, regarding the outer maps, only ID1 has to be taken into account; in turn, the operation has to involve the inner (common) keys '2016-10-02' and '2016-10-03'.
Here my current code
Set<Integer> common_keys = new LinkedHashSet<Integer>(map1.keySet());
common_keys.retainAll(map2.keySet());
System.out.println("Common keys: "+common_keys.toString());
map1.forEach((k,v) -> {
Map<String,Double> submap_1 = new LinkedHashMap<String,Double>(v);
Map<String,Double> submap_2 = new LinkedHashMap<String,Double>(map2.get(k));
Set<String> commons = new LinkedHashSet<String>(submap_1.keySet());
commons.retainAll(submap_2.keySet());
System.out.println(k+" : common days: "+commons);
List<Double> val1 = submap_1.keySet()
.stream()
.filter(c -> commons.contains(c))
.map(c -> submap_1.get(c))
.collect(Collectors.toList());
List<Double> val2 = submap_2.keySet()
.stream()
.filter(c -> commons.contains(c))
.map(c -> submap_2.get(c))
.collect(Collectors.toList());
List<Double> ABS = IntStream.range(0, val1.size())
.mapToObj(i -> val1.get(i) - val2.get(i))
.collect(Collectors.toList());
diff_abs.put(k, ABS);
});
Is there a simpler and smart way to do this, by using JAVA 8' STREAM API?
Thanks in advance
Upvotes: 3
Views: 1314
Reputation: 298419
You can create a re-usable method for merging two maps by retaining common keys only and applying a function to both values:
public static <K,V,R> Map<K, R> merge(Map<K,V> map1, Map<K,V> map2, BiFunction<V,V,R> f) {
boolean hasOrder=map1.entrySet().spliterator().hasCharacteristics(Spliterator.ORDERED);
return map1.entrySet().stream()
.collect(hasOrder? LinkedHashMap<K,R>::new: HashMap<K,R>::new, (m,e)-> {
V v2 = map2.get(e.getKey());
if(v2!=null) m.put(e.getKey(), f.apply(e.getValue(), v2));
}, Map::putAll);
}
Then, you can easily implement the merging of nested maps by using the method twice:
public static <K1, K2> Map<K1, List<Double>> merge(
Map<K1, Map<K2, Double>> map1, Map<K1, Map<K2, Double>> map2) {
return merge(map1, map2,
(a,b) -> new ArrayList<>(merge(a, b, (x,y) -> x-y).values()));
}
Note that the code above is smart enough to create a LinkedHashMap
if, and only if, the first map has an intrinsic order, e.g. is itself a LinkedHashMap
or SortedMap
, and a plain HashMap
otherwise. The order of the second map is not retained anyway, which is not an issue if, as your original code seems to assume, both maps have the same order. If their order differs, there’s obviously no way to retain both orders.
Upvotes: 2
Reputation: 82461
Creating the Set
is unnecessary. Replace this with a check for null
in for the same key in the other map. Furthermore creating copies of the maps is unnecessary, since the maps are never modified in the method.
public static <T, U> Map<T, List<Double>> merge(Map<T, Map<U, Double>> m1, Map<T, Map<U, Double>> m2) {
Map<T, List<Double>> result = new HashMap<>();
m1.forEach((k, v) -> {
Map<U, Double> v2 = m2.get(k);
if (v2 != null) {
// both outer maps contain the same key
ArrayList<Double> list = new ArrayList<>();
v.forEach((innerK, innerV) -> {
Double d = v2.get(innerK);
if (d != null) {
// matching key in both inner maps
list.add(innerV - d);
}
});
// list.trimToSize();
result.put(k, list);
}
});
return result;
}
Upvotes: 4
Reputation: 10322
Just a few points on your example above. My go-to implementations for List
, Set
, and Map
are ArrayList
, HashSet
, and HashMap
unless I need something specifically provided by their Linked
implementations, aka, consistent order (but this happens exceedingly rarely).
Here's how I would approach this:
public static <T, S> Map<T, List<Double>> method(
Map<T, Map<S, Double>> map1,
Map<T, Map<S, Double>> map2)
{
Set<T> commonKeys = intersection(map1.keySet(), map2.keySet());
return commonKeys.stream().collect(Collectors.toMap(
Function.identity(),
key -> {
Map<S, Double> inner1 = map1.get(key);
Map<S, Double> inner2 = map2.get(key);
Set<S> innerCommonKeys = intersection(
inner1.keySet(),
inner2.keySet());
return innerCommonKeys.stream().map(innerKey ->
inner1.get(innerKey) - inner2.get(innerKey))
.collect(Collectors.toList());
}));
}
private static <T> Set<T> intersection(Set<T> set1, Set<T> set2) {
Set<T> intersection = new HashSet<T>(set1);
intersection.retainAll(set2);
return intersection;
}
And here is a "test" to show that it works:
public static void main(String[] args)
{
Map<String, Map<String, Double>> map1 = new HashMap<>();
Map<String, Double> inner11 = new HashMap<>();
inner11.put("2016-10-02", 10.0);
inner11.put("2016-10-03", 20.0);
inner11.put("2016-10-04", 30.0);
map1.put("ID1", inner11);
Map<String, Double> inner12 = new HashMap<>();
inner12.put("2016-10-02", 1.00);
inner12.put("2016-10-03", 2.00);
inner12.put("2016-10-04", 3.00);
map1.put("ID2", inner12);
Map<String, Map<String, Double>> map2 = new HashMap<>();
Map<String, Double> inner21 = new HashMap<>();
inner21.put("2016-10-02", 2.00);
inner21.put("2016-10-03", 3.00);
map2.put("ID1", inner21);
Map<String, Double> inner22 = new HashMap<>();
inner22.put("2016-10-02", 1.00);
inner22.put("2016-10-03", 2.00);
inner22.put("2016-10-04", 3.00);
map2.put("ID3", inner22);
System.out.println(method(map1, map2));
}
Output: {ID1=[8.0, 17.0]}
Upvotes: 1