减少/收集展平地图的流,特别是遍历顺序(不是深度优先搜索)

时间:2017-02-27 14:23:08

标签: java collections java-8 java-stream

我正在检查以我的特定遍历顺序(不是深度优先搜索)缩小/收集展平地图的所有内容,并且无法为此类操作找到示例/收集器签名。我将尝试用一个简化的例子来解释它:

我有3张地图

Map1: {1,"a1"; 2, "b1"; 3, "c1"}
Map2: {1,"a2", 2, "b2"; 3, "c2"}
Map3: {1,"a3"; 2, "b3"; 3, "c3"}

我想最终将其收集如下:

LinkedHashSet() {new LinkedHashSet("a1","a2","a3"), 
                 new LinkedHashSet("b1","b2","b3"), 
                 new LinkedHashSet("c1","c2","c3")}

如果我使用flatMap,我会有1,2,3,1,2,3,1,2,3而不是1,1,1,2,2,2,3,3,3,所以我正在尝试某种脏的分组,但无法生存到所有的签名。我正在尝试这样的事情但我坦率地迷路了,这一定是一种更简单的方法:

...flatMap( map-> map.stream() )
.collect( Collectors.groupingBy(Map.Entry::getKey, Collectors.reducing(Map.Entry::getValue, Collectors.toCollection(LinkedHashSet::new)))

(这只是一个简化)

2 个答案:

答案 0 :(得分:1)

正如Holger正确注意到的那样,我最初错过了关于遍历顺序的观点。

但是,如果您的初始地图(Map1, Map2, Map3)是有序地图的某些实例,例如LinkedHashMapSortedMap,则遍历顺序才有意义。

我会尝试解释。假设这个例子(使用jdk-9新的不可变映射 - 在内部使用随机化模式,这意味着你不能保证获得条目的顺序。)

    Map<Integer, String> map1 = Map.of(3, "a1", 2, "b1", 1, "c1");
    Map<Integer, String> map2 = Map.of(3, "a2", 2, "b2", 1, "c2");
    Map<Integer, String> map3 = Map.of(3, "a3", 2, "b3", 1, "c3");

    System.out.println(Stream.of(map1, map2, map3)
           .flatMap(m -> m.entrySet().stream())
           .collect(Collectors.toList()));

此操作的结果可能是这两个例子。

  

[2 = b1,3 = a1,1 = c1,2 = b2,3 = a2,1 = c2,2 = b3,3 = a3,1 = c3]

     

[1 = c1,3 = a1,2 = b1,1 = c2,3 = a2,2 = b2,1 = c3,3 = a3,2 = b3]

此时HashMap没有内部完成任何类型的随机化或任何其他可能在迭代时改变条目顺序的功能,但可能会在将来的版本中发生 ,所以你不能依赖它。

因此,假设您的地图是LinkedHashMaps:

Set<Set<String>> result = Stream.of(map1, map2, map3).flatMap(e -> e.entrySet().stream())
            .collect(Collectors.collectingAndThen(Collectors.toMap(
                    e -> e.getKey(),
                    e -> {
                        Set<String> l = new LinkedHashSet<>();
                        l.add(e.getValue());
                        return l;
                    },
                    (left, right) -> {
                        left.addAll(right);
                        return left;
                    },
                    LinkedHashMap::new),
                    map -> map.values().stream().collect(Collectors.toCollection(LinkedHashSet::new))));

答案 1 :(得分:1)

您的原始方法并不是那么糟糕,但您内部的错误很少。不幸的是,当前的编译器倾向于使用关于通用签名不匹配的报告来填充错误消息,这使得很容易忽略诸如未找到的方法或类型之类的更简单的错误,这些错误通常是问题的实际原因。

首先,stream()中没有Map方法。您必须决定要汇总的集合视图,keySet()entrySet()values()。显然,您希望使用map-> map.entrySet().stream()代替map-> map.stream()

其次,您使用的是Collectors.reducing,而不是显而易见的Collectors.mapping

修复这些错误应该足以让通用签名错误消失; groupingBy收集器的结果是Map<Integer, LinkedHashSet<String>>然后,但不幸的是,默认情况下它不是维护地图的订单。因此,您必须添加地图Supplier,即LinkedHashMap::new,以强制使用维护Map的订单。

然后,您必须将结果地图的values()复制到LinkedHashSet以获得所需类型的结果,即new LinkedHashSet<>(resultMap.values())

总而言之,我们得到:

Map<Integer,String> map1 = map(1,"a1", 2,"b1", 3,"c1");
Map<Integer,String> map2 = map(1,"a2", 2,"b2", 3,"c2");
Map<Integer,String> map3 = map(1,"a3", 2,"b3", 3,"c3");

Map<Integer, LinkedHashSet<String>> m = Stream.of(map1, map2, map3)
    .flatMap( map -> map.entrySet().stream() )
    .collect( Collectors.groupingBy(Map.Entry::getKey,
        LinkedHashMap::new,
        Collectors.mapping(Map.Entry::getValue,
            Collectors.toCollection(LinkedHashSet::new))));
LinkedHashSet<LinkedHashSet<String>> result=new LinkedHashSet<>(m.values());
// [[a1, a2, a3], [b1, b2, b3], [c1, c2, c3]]
System.out.println(result);

static <K,V> Map<K,V> map(K k1, V v1, K k2, V v2, K k3, V v3) {
    LinkedHashMap<K,V> m=new LinkedHashMap<>();
    m.put(k1, v1);
    m.put(k2, v2);
    m.put(k3, v3);
    return m;
}

我希望,您仍然可以在更改的代码中识别您的原始方法。 您还可以通过Collector

将后处理步骤集成到collectingAndThen
LinkedHashSet<LinkedHashSet<String>> result = Stream.of(map1, map2, map3)
    .flatMap( map -> map.entrySet().stream() )
    .collect( Collectors.collectingAndThen(
        Collectors.groupingBy(Map.Entry::getKey,
            LinkedHashMap::new,
            Collectors.mapping(Map.Entry::getValue,
                Collectors.toCollection(LinkedHashSet::new))),
        m -> new LinkedHashSet<>(m.values())));

应该提到的是,如果值都是唯一的,则此结果将仅显示为矩形,如原始地图的转置表。与任何其他LinkedHashSet一样,Set将消除重复的元素。这不仅适用于子集,也适用于将消除相同子集的结果集。