Есть ли способ проверить, содержит ли поток все элементы коллекции? - PullRequest
3 голосов
/ 07 октября 2019

Например, мне нужно что-то вроде:

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

Конечно, я мог бы накапливать все элементы потока в другой Collection, используя метод collect() и вызов Collection.containsAll(), но чтоесли поток слишком большой и все его элементы неэффективны?

Ответы [ 3 ]

4 голосов
/ 07 октября 2019

Это должно сработать:

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 является параллельным, вам придется использовать параллельный хэш-набор. Подробнее см. этот пост )
  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());

Код немного изменился, но идея та же.

3 голосов
/ 07 октября 2019

Независимо от размера Stream вам придется обрабатывать все его элементы, если он не содержит все элементы Collection.

. Вы можете сэкономить время обработки, если маленькийПрефикс Stream содержит все элементы Collection, а Collection намного меньше, чем 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();

Обратите внимание, что если Stream может содержать несколько копийтот же элемент Collection, возможно, вам придется добавить операцию .distinct() после filter().

0 голосов
/ 09 октября 2019
boolean allMatch = stream.map(Object::toString)
            .allMatch(s -> collection.contains(s));  

Метод allMatch работает аналогично anyMatch, но проверяет, соответствуют ли все элементы потока указанному предикату.

...