Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 257
Fab Avatar asked Jul 06 '26 19:07

Fab


2 Answers

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;
}
like image 158
fabian Avatar answered Jul 08 '26 10:07

fabian


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.

like image 44
Holger Avatar answered Jul 08 '26 08:07

Holger



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!