有没有一种方法可以检查Stream是否包含所有集合元素?

时间:2019-10-07 12:33:53

标签: java collections java-8 java-stream contains

例如,我需要类似的东西:

Collection<String> collection = /* ... */;
Stream<Object> stream = /* ... */;
boolean containsAll = stream.map(Object::toString).containsAll(collection);

当然,我可以使用Collection方法和调用collect()将流的所有元素累积到另一个Collection.containsAll()中,但是如果流太大而效率低下,该怎么办?处理所有元素?

4 个答案:

答案 0 :(得分:4)

这应该可以解决问题:

Set<String> set = new HashSet<>(collection);
boolean containsAll = set.isEmpty() || stream.map(Object::toString)
                                             .anyMatch(s -> set.remove(s) && set.isEmpty());

解决方案可能看起来令人困惑,但是这个想法很简单:

  1. 为了防止在collection上进行多次迭代,我们将其包装到HashSet中。 (如果您的stream是并行的,则必须使用并发哈希集。有关更多详细信息,请参见this post
  2. 如果collection(或set)为空,则我们返回true而不处理stream
  3. 对于stream的每个条目,我们尝试将其从set中删除。如果Set::remove的结果为true(因此它包含在set中)并且set在删除后为空,我们可以得出结论,stream包含初始collection的所有元素。
  4. 终端操作Stream::anyMatch是一个短路操作。因此,一旦stream为空,它将停止在set上进行迭代。在最坏的情况下,我们将处理整个流。

也许这是一种更具可读性的形式:

Set<String> set = new HashSet<>(collection);
boolean containsAll = set.isEmpty() || stream.map(Object::toString)
                                             .filter(set::remove)
                                             .anyMatch(__ -> set.isEmpty());

如果collection可以包含重复项,并且需要检查stream是否包含所有重复项,那么我们将需要维护计数器的并发映射。

Map<String, AtomicLong> map = new ConcurrentHashMap<>();
collection.forEach(s -> map.computeIfAbsent(s, __ -> new AtomicLong()).incrementAndGet());
boolean containsAll = map.isEmpty() || stream.map(Object::toString)
                                             .filter(map::containsKey)
                                             .filter(s -> map.get(s).decrementAndGet() == 0)
                                             .filter(s -> map.remove(s) != null)
                                             .anyMatch(__ -> map.isEmpty());

代码稍有变化,但思路相同。

答案 1 :(得分:3)

无论Stream有多大,如果它不包含Collection的所有元素,则必须处理其所有元素。

如果Stream的小前缀包含Collection的所有元素,并且CollectionStream小得多,则可以节省处理时间。

boolean containsAll = 
    stream.map(Object::toString)
          .filter(s -> collection.contains(s)) // it would be wise to convert collection to a Set
          .limit(collection.size())
          .count() == collection.size();

请注意,如果Stream可能包含Collection相同元素的多个副本,则可能必须在.distinct()之后添加filter()操作。

答案 2 :(得分:2)

boolean allMatch = stream.map(Object::toString)
            .allMatch(s -> collection.contains(s));  

allMatch方法的工作方式与anyMatch相似,但是将检查流中的所有元素是否与给定谓词匹配。

答案 3 :(得分:0)

Collection<String>创建集合,以加快搜索操作O(1)

Set<String> set = new HashSet<>(collection);

然后使用allMatch检查流中是否包含集合中的每个项目

boolean containsAll = stream.map(Object::toString)
                            .allMatch(s -> set.contains(s));

另一种方法是不包含在集合中,并使用limit(1)进行优化

boolean isContains = stream.map(Object::toString)
                           .filter(s -> !set.contains(s))
                           .limit(1)
                           .count() > 0;