Is there a way to check if a Stream contains all collection elements?
Create a Set from Collection<String>
to make search operation faster O(1)
Set<String> set = new HashSet<>(collection);
Then use allMatch
to check every item in stream contains in set or not
boolean containsAll = stream.map(Object::toString)
.allMatch(s -> set.contains(s));
Another way :
Filter by not contained in set and use limit(1)
for optimization
boolean isContains = stream.map(Object::toString)
.filter(s -> !set.contains(s))
.limit(1)
.count() > 0;
Regardless of how big the Stream
is, you will have to process all of its elements if it does not contain all the elements of the Collection
.
You could save processing time if a small prefix of the Stream
contains all the elements of Collection
, and the Collection
is much smaller than the Stream
.
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();
Note that if the Stream
may contain multiple copies of the same element of the Collection
, you may have to add a .distinct()
operation after the filter()
.
This should do the trick:
Set<String> set = new HashSet<>(collection);
boolean containsAll = set.isEmpty() || stream.map(Object::toString)
.anyMatch(s -> set.remove(s) && set.isEmpty());
The solution might look confusing, but the idea is straightforward:
- In order to prevent multiple iterations over
collection
we wrap it into aHashSet
. (In case yourstream
is a parallel one, then you will have to use a concurrent hash set. See this post for more details) - If the
collection
(orset
) is empty then we returntrue
without processing thestream
- For each entry of
stream
we try to remove it fromset
. In case the result ofSet::remove
istrue
(hence it was contained byset
) and theset
is empty after removal, we can conclude thatstream
contained all the elements of initialcollection
. - The terminal operation
Stream::anyMatch
is a short-circuiting one. So it will stop iterating overstream
once theset
is empty. In worst case we will process the entire stream.
Perhaps this is a bit more readable form:
Set<String> set = new HashSet<>(collection);
boolean containsAll = set.isEmpty() || stream.map(Object::toString)
.filter(set::remove)
.anyMatch(__ -> set.isEmpty());
If the collection
can contain duplicates and there is a requirement to check if stream
contains all of them, then we will need to maintain a concurrent map of counters.
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());
The code slightly changed but the idea is the same.