【问题标题】:Intersection of stream of sets into new set集合流与新集合的交集
【发布时间】:2016-07-08 12:07:57
【问题描述】:

有没有更好、更简单的方法来解决这个问题?

@Test
public void testReduce() {
    Set<Integer> foo = ImmutableSet.of(1,2,3,4,8,9);
    Set<Integer> bar = ImmutableSet.of(1,3,8,5,11);

    //DO think about solution for 1..n sets, and not only two.
    Set<Integer> intersection = ImmutableList.of(foo,bar)
            .stream()
            .reduce( null, (a, b) -> {
                if ( a == null ) {
                    a = new HashSet<Integer>(b);
                }
                else {
                    a.retainAll(b);
                }
                return a;
            });
    assertThat( intersection, is( ImmutableSet.of( 1,3,8) ) );
}

【问题讨论】:

  • 您可以简单地使用 ImmutableList.of(foo,bar).stream() 而不是 Stream.of(foo,bar)...
  • @Wilson:我看到了这个问题。我需要一个小溪交叉口,而不仅仅是两组
  • @Holger:谢谢。这只是一个示例代码。在实际代码中,我希望将一组集合作为输入

标签: java-8 java-stream


【解决方案1】:

reduce 是错误的方法,因为你不能以这种方式修改函数的参数。这是一个可变缩减,也称为collect

List<Set<Integer>> listOfSets=…;

if (listOfSets.isEmpty()) {
  return new HashSet<>();
}

Set<Integer> intersection = listOfSets.stream().skip(1)
    .collect(()->new HashSet<>(listOfSets.get(0)), Set::retainAll, Set::retainAll);

必须查看第一组是这里的一个缺点,但是使用null 作为标识值也不是干净的(并且不适用于collect,因为累加器无法返回新组)。

【讨论】:

  • 哇。很好的解决方案。一个问题:我们可以跳过流中的第一个元素。有可能吗?
  • 由于您要走十字路口,skip(1) 并不是真正必要的,但性能稍好一些。对于较大的流,除非第一组的元素比其他组多得多,否则差异可以忽略不计。
【解决方案2】:

如果您使用Eclipse Collections,以下将起作用:

@Test
public void testReduce()
{
    ImmutableSet<Integer> foo = Sets.immutable.of(1, 2, 3, 4, 8, 9);
    ImmutableSet<Integer> bar = Sets.immutable.of(1, 3, 8, 5, 11);

    // Works with Eclipse Collections 7.0 or above
    ImmutableSet<Integer> intersection1 = Lists.mutable.of(foo, bar)
            .stream()
            .reduce(ImmutableSet::intersect).get();
    Assert.assertEquals(intersection1, Sets.immutable.of(1, 3, 8));

    // Works with Eclipse Collections 8.0.0-M1 or above
    ImmutableSet<Integer> intersection2 = Lists.immutable.of(foo, bar)
            .reduce(ImmutableSet::intersect).get();
    Assert.assertEquals(intersection2, Sets.immutable.of(1, 3, 8));
}

这也适用于MutableSet

@Test
public void testReduce()
{
    MutableSet<Integer> foo = Sets.mutable.of(1, 2, 3, 4, 8, 9);
    MutableSet<Integer> bar = Sets.mutable.of(1, 3, 8, 5, 11);

    // Works with Eclipse Collections 7.0 or above
    MutableSet<Integer> intersection1 = Lists.mutable.of(foo, bar)
            .stream()
            .reduce(MutableSet::intersect).get();
    Assert.assertEquals(intersection1, Sets.immutable.of(1, 3, 8));

    // Works with Eclipse Collections 8.0.0-M1 or above
    MutableSet<Integer> intersection2 = Lists.immutable.of(foo, bar)
            .reduce(MutableSet::intersect).get();
    Assert.assertEquals(intersection2, Sets.immutable.of(1, 3, 8));
}

在 Eclipse Collections 中,ImmutableSet 不扩展 java.util.Set,因为 Set 是一个可变接口。 MutableSet 确实扩展了 java.util.Set。此设计选择在此question 的答案中进行了说明。

注意:我是 Eclipse Collections 的提交者。

【讨论】:

  • 很好的例子。我将看看 Eclipse Collections。看起来很有趣。
【解决方案3】:

此答案仅适用于整数集,不适用于泛型集。但是对于寻求速度的人来说,有时整数列表是压缩位图的好例子。你应该检查你的整数组是否很好,在某些情况下,如果你这样做,你可以在速度上赢得几个数量级(使用 com.googlecode.javaewah32,Apache 2.0 许可证):

    Set<Integer> foo = ImmutableSet.of(1,2,3,4,8,9);
    Set<Integer> bar = ImmutableSet.of(1,3,8,5,11);

    EWAHCompressedBitmap32 fooBitmap = new EWAHCompressedBitmap32();
    EWAHCompressedBitmap32 barBitmap = new EWAHCompressedBitmap32();

    //fill bitmaps
    foo.stream().forEach(fooBitmap::set);
    bar.stream().forEach(barBitmap::set);

    //fooBitmap.and(barBitmap) returns intersection of sets now. fast!
    ImmutableSet<Integer> intersection = ImmutableSet.<Integer>builder()
                                    .addAll(fooBitmap.and(barBitmap))
                                    .build();

    System.out.println(intersection);

此代码只是一个示例。您可能/应该使用不同的方法转换为结果集。 EWAHCompressedBitmap32Iterable&lt;Integer&gt; 所以,想象力是无限的。

现在,上面的代码只与 2 个集合相交。要与列表中的所有集合相交,您可以执行通常的 reduce:

    Set<Integer> foo = ImmutableSet.of(1,2,3,4,8,9);
    Set<Integer> bar = ImmutableSet.of(1,3,8,5,11);

    List<Set<Integer>> sets = ImmutableList.of(foo,bar);

    EWAHCompressedBitmap32 res = sets.stream().map(l -> {
        EWAHCompressedBitmap32 b = new EWAHCompressedBitmap32();
        l.stream().forEach(b::set);
        return b;
    }).reduce(null, (l, r) -> l == null ? r : l.and(r));

    System.out.println(res);

另一种选择是使用归约收集器:

EWAHCompressedBitmap32 res = sets.stream().collect(Collectors.reducing(
      //identity
      null, 
      //mapper set -> compressedBitmap
      l -> { 
          EWAHCompressedBitmap32 b = new EWAHCompressedBitmap32();
          l.stream().forEach(b::set);
          return b;
      },
      //and-reducer 
      (l, r) -> l == null ? r : l.and(r) 
 ));

【讨论】:

    【解决方案4】:

    您可以通过检查barotherBar 集合中的元素来过滤foo 的元素:

    Set<Integer> set =
        foo.stream().filter(e -> Stream.of(bar, otherBar).allMatch(s -> s.contains(e))).collect(toSet());
    

    例如,如果您收到Collection&lt;Set&lt;T&gt;&gt;,如您所说:

    public static <T> Set<T> intersection(Collection<Set<T>> input) {
        if(input.isEmpty()) {
            return Collections.emptySet();
        } else {
            Set<T> first = input.iterator().next();
            //if the collection allows to remove element, you can remove first before
            return first.stream().filter(e -> input.stream().allMatch(s -> s.contains(e))).collect(toSet());
        }
    }
    

    【讨论】:

    • 在示例中我有 2 套。该解决方案应该适用于任意数量的元素,并且应该避免在每次 reducer 运行时创建一个新的 Set。
    【解决方案5】:

    使用Google GuavaApache Commons 有一种更短的方法来实现:

    Set<Integer> intersection = Stream.of(foo,bar)
                                      .reduce(Sets::intersection)
                                      .orElse(SetUtils.emptySet());
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-26
      • 1970-01-01
      相关资源
      最近更新 更多