Fab Fab - 2 months ago 13
Java Question

Performing an operation between two HashMaps by using STREAM API

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

Answer

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]}