使用STREAM API在两个HashMaps之间执行操作

时间:2016-10-05 09:40:51

标签: java hashmap java-8 java-stream

我有

形式的两个HashMaps
Map<Integer, Map<String,Double>> MAP_1
Map<Integer, Map<String,Double>> MAP_2

让INNER成为内部地图,即

Map<String,Double>

对于每对相应的键,我想在两个内部映射之间执行操作(例如减法)。反过来,该操作应该在内部地图的两个相应键之间执行。 生成的地图应为

Map<Integer,List<Double> RESULT

示例地图

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

因此,对于外部地图,只需要考虑ID1;反过来,操作必须涉及内部(公共)键'2016-10-02'和'2016-10-03'。

这是我目前的代码

 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);
        });

使用JAVA 8'STREAM API是否有更简单明智的方法?

提前致谢

3 个答案:

答案 0 :(得分:4)

创建Set是不必要的。将其替换为null in,以获取其他地图中的相同密钥。此外,创建地图的副本是不必要的,因为地图永远不会在方法中被修改。

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

答案 1 :(得分:1)

上面的例子中只有几点。我对ListSetMap的首选实施是ArrayListHashSetHashMap,除非我需要由他们的Linked实现,又称一致的顺序(但这种情况极少发生)。

以下是我如何做到这一点:

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

这是一个&#34;测试&#34;表明它有效:

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));
}

输出:{ID1=[8.0, 17.0]}

答案 2 :(得分:1)

您可以通过仅保留公共密钥并将函数应用于这两个值来创建可重用的方法来合并两个地图:

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);
}

然后,您可以使用该方法两次轻松实现嵌套地图的合并:

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()));
}

请注意,上面的代码非常智能,只有在第一张地图具有内在顺序时才能创建LinkedHashMap,例如本身就是LinkedHashMapSortedMap,另外还有普通HashMap。无论如何都不会保留第二张地图的顺序,如果原始代码似乎假设两个地图具有相同的顺序,则这不是问题。如果他们的订单不同,显然没有办法保留两个订单。