Что-то вроде «содержит что-нибудь» для набора Java?


307

У меня есть два набора, A и B, одного типа.

Я должен найти, содержит ли A какой-либо элемент из множества B.

Что было бы лучшим способом сделать это без перебора наборов? В библиотеке Set есть contains(object)и containsAll(collection), но нет containsAny(collection).


4
Вы пытаетесь избежать итерации из соображений эффективности или из-за чистоты кода?
ишавит

Ответы:


527

Не Collections.disjoint(A, B)сработает? Из документации:

Возвращает, trueесли две указанные коллекции не имеют общих элементов.

Таким образом, метод возвращает, falseесли коллекции содержат какие-либо общие элементы.


17
Предпочитайте это другим решениям, потому что это не изменяет ни один из наборов или создает новое.
devconsole

7
И является стандартным JRE, и работает с любыми коллекциями, а не только с установленными.
Пьер Генри

4
Я не думаю, что это самое быстрое, оно не будет закорачиваться, когда найден первый элемент пересечения.
Бен Хорнер

7
На самом деле он будет
закорочен,


156

Stream::anyMatch

С Java 8 вы можете использовать Stream::anyMatch.

setA.stream().anyMatch(setB::contains)

1
Это именно то, что я искал! Спасибо :-) Я также не знал, что вы можете использовать переменные с синтаксисом ::!
Дантистон

1
@blevert, не могли бы вы объяснить, что происходит внутри anyMatch?
Криштиану

8
@Cristiano здесь, anyMatchбудет транслировать все элементы setAи вызывать их setB.contains()все. Если «true» возвращается для любого из элементов, выражение в целом будет иметь значение true. Надеюсь, это помогло.
Алексей Вулай


31

Хороший способ реализовать containsAny для множеств - использовать Guava Sets.intersection () .

containsAnyвернул бы boolean, поэтому вызов выглядит так:

Sets.intersection(set1, set2).isEmpty()

Это возвращает истину, если множества не пересекаются, иначе ложь. Временная сложность этого, вероятно, немного лучше, чем retainAll, потому что вам не нужно делать клонирование, чтобы избежать изменения вашего исходного набора.


3
Единственный недостаток использования этого подхода - вы должны включать библиотеки гуавы. Что, я думаю, не является недостатком, потому что API коллекции Google очень сильны.
Мохаммад Аднан

@DidierL большинство функций утилит Guava Collections, включая эту, возвращают представления структур данных. Таким образом, в этом случае не нужно беспокоиться о «создании набора». Реализация интересно читать здесь, и / или увидеть JavaDoc: google.github.io/guava/releases/21.0/api/docs/com/google/common/...
Chut

@MohammadAdnan Еще одним недостатком является то, что он вычисляет полное пересечение - если set1 и set2 очень большие, это будет намного более ресурсоемким (как с точки зрения процессора, так и с точки зрения памяти), чем просто проверка, имеют ли они какой-либо общий элемент.
Марксама


16

Я использую org.apache.commons.collections.CollectionUtils

CollectionUtils.containsAny(someCollection1, someCollection2)

Вот и все! Возвращает true, если хотя бы один элемент находится в обеих коллекциях.

Прост в использовании, а название функции более наглядно.


5

Используйте retainAll()в интерфейсе Set. Этот метод обеспечивает пересечение элементов, общих в обоих наборах. См. API документы для получения дополнительной информации.


Если цель избежать итерации для эффективности, retainAllвероятно, не поможет. Его реализация в AbstractCollectionитерациях.
ишавит

1
ишавит правильно. Учитывая, что OP ищет, существует ли какой-либо элемент в обоих наборах, надлежащий алгоритм будет иметь время O(1)выполнения в лучшем случае, в то время как retainAllбудет иметь что-то подобное O(N)(это будет зависеть от размера только 1 набора) лучшее время выполнения.
Зейчин

3

Я бы порекомендовал создать HashMapиз набора A, а затем выполнить итерацию по набору B и проверить, есть ли какой-либо элемент B в A. Это будет выполняться во O(|A|+|B|)времени (так как не будет столкновений), тогда как retainAll(Collection<?> c)должно выполняться во O(|A|*|B|)времени.


3

Есть немного грубый способ сделать это. Если и только если набор A содержит некоторый элемент B, чем вызов

A.removeAll(B)

изменит набор А. В этом случае removeAll вернет true (как указано в документации по removeAll ). Но, вероятно, вы не хотите изменять набор A, поэтому вы можете подумать, что будете действовать с копией, вот так:

new HashSet(A).removeAll(B)

и возвращаемое значение будет истинным, если множества не различны, то есть они имеют непустое пересечение.

Также см. Коллекции Apache Commons


2

Вы можете использовать метод retainAll и получить пересечение двух ваших множеств.


В большинстве случаев необходимо сохранить исходный набор, поэтому для retainAllего использования необходимо сделать копию исходного набора. Тогда это более эффективно для использования, HashSetкак предлагает Зейчин .
Петр Пудлак

Это изменение состояния, а не проверка состояния
Бен
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.