Перевірте, чи масив є підмножиною іншого


145

Будь-яка ідея, як перевірити, чи є цей список підмножиною іншого?

Конкретно в мене є

List<double> t1 = new List<double> { 1, 3, 5 };
List<double> t2 = new List<double> { 1, 5 };

Як перевірити, що t2 є підмножиною t1, використовуючи LINQ?


Якщо списки відсортовані (як у вашому прикладі), це повинно бути можливим за O (n + m) час.
Полковник Паніка

Відповіді:


255
bool isSubset = !t2.Except(t1).Any();

1
Я створив метод розширення geekswithblogs.net/mnf/archive/2011/05/13/…
Майкл Фрейджім

@Bul Ikana Робота з цим кодом проста, метод розширення внутрішньо викликає рівняння рівнянь та GetHashCode перекритих методів класу об'єктів, якщо для роботи не передбачено IEqualityComparer.
Mrinal Kamboj

2
Якщо списки довжини n та m, яка складність у часі цього алгоритму?
Полковник Паніка

2
Було б добре, якби це було зведено до методу linq під назвою ContainsAll
Себастьян Паттен

60

Використовуйте HashSet замість List, якщо працюєте з наборами. Тоді ви можете просто використовувати IsSubsetOf ()

HashSet<double> t1 = new HashSet<double>{1,3,5};
HashSet<double> t2 = new HashSet<double>{1,5};

bool isSubset = t2.IsSubsetOf(t1);

Вибачте, що він не використовує LINQ. :-(

Якщо вам потрібно використовувати списки, тоді рішення Джареда працює з застереженням, що вам потрібно буде видалити всі повторювані елементи, які існують.


3
Саме так. Ви хочете встановити операцію, використовуйте клас, призначений для них. Рішення Кемерона творче, але не таке чітке / виразне, як HashSet.
технофіл

2
Гм, я не згоден, тому що питання спеціально говорить "використовувати LINQ".
JaredPar

9
@JaredPar: То що? Хіба не краще показати комусь правильний шлях, ніж той, який вони хочуть пройти?
Джонатан Аллен

Список підтримує свій порядок, але набір - ні. Якщо порядок важливий, це дасть неправильні результати.
UuDdLrLrSs

11

Якщо ви перевіряєте одиницю, ви також можете використовувати метод CollectionAssert.IsSubsetOf :

CollectionAssert.IsSubsetOf(subset, superset);

У наведеному вище випадку це означатиме:

CollectionAssert.IsSubsetOf(t2, t1);

7

Це значно ефективніше рішення, ніж інші, розміщені тут, особливо верхнє рішення:

bool isSubset = t2.All(elem => t1.Contains(elem));

Якщо ви можете знайти один елемент в t2, який не в t1, то ви знаєте, що t2 не є підмножиною t1. Перевага цього методу полягає в тому, що він робиться все на місці, не виділяючи додаткового простору, на відміну від рішень, що використовують .Except або .Intersect. Крім того, це рішення може розірватися, як тільки він знайде один елемент, який порушує умову підмножини, а інші продовжують пошук. Нижче наведена оптимальна довга форма рішення, яка в моїх тестах лише незначно швидша, ніж вищенаведене скорочене рішення.

bool isSubset = true;
foreach (var element in t2) {
    if (!t1.Contains(element)) {
        isSubset = false;
        break;
    }
}

Я зробив кілька принципових аналіз ефективності всіх рішень, і результати є різкими. Ці два рішення приблизно в 100 разів швидше, ніж рішення .Except () та .Intersect (), і не використовують додаткової пам'яті.


Саме це і !t2.Except(t1).Any()робиться. Linq працює вперед. Any()запитує, IEnumerableчи є принаймні один елемент. У цьому сценарії t2.Except(t1)випускається лише перший елемент, t2якого немає t1. Якщо перший елемент t2не в t1ньому, він закінчується найшвидше, якщо всі елементи t2в t1ньому знаходяться найдовше.
до

У той час як грати з яким - то орієнтиром, я дізнався, коли ви берете t1={1,2,3,...9999}і t2={9999,9998,99997...9000}, ви отримуєте наступні вимірювання: !t2.Except(t1).Any(): 1ms -> t2.All(e => t1.Contains(e)): 702ms. І чим гірше, тим більший асортимент.
ABTO

2
Це не спосіб, яким працює Linq. t2.Except (t1)повертається IEnumerableне a Collection. Він випромінює тільки всі можливі пункти , якщо ви ітерацію повністю над ним, наприклад, ToArray ()або ToList ()чи використання , foreachне порушуючи всередині. Шукайте відкладене виконання linq, щоб прочитати більше про цю концепцію.
ABTO

1
Я повністю знаю, як працює відкладене виконання у Linq. Ви можете відкладати виконання всього, що вам потрібно, але коли ви хочете визначити, чи t2 є підмножиною t1, вам потрібно буде переглядати весь список, щоб розібратися в ньому. Не обійти цей факт.
користувач2325458

2
Давайте візьмемо приклад з коментаря t2={1,2,3,4,5,6,7,8} t1={2,4,6,8} t2.Except(t1)=> перший елемент t2 = 1 => різниця від 1 до t1 дорівнює 1 (перевірено проти {2,4,6,8}) => Except()випускає перший елемент 1 => Any()отримує елемент => Any()приводить до істинної => додаткової перевірки елементів у t2.
ABTO

6

@ Рішення Кемерона як метод розширення:

public static bool IsSubsetOf<T>(this IEnumerable<T> a, IEnumerable<T> b)
{
    return !a.Except(b).Any();
}

Використання:

bool isSubset = t2.IsSubsetOf(t1);

(Це схоже, але не зовсім те саме, що розміщено в блозі @ Майкла)


0

Спираючись на відповіді від @Cameron та @Neil, я написав метод розширення, який використовує ту саму термінологію, що і клас Enumerable.

/// <summary>
/// Determines whether a sequence contains the specified elements by using the default equality comparer.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <param name="source">A sequence in which to locate the values.</param>
/// <param name="values">The values to locate in the sequence.</param>
/// <returns>true if the source sequence contains elements that have the specified values; otherwise, false.</returns>
public static bool ContainsAll<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> values)
{
    return !values.Except(source).Any();
}

0

Тут ми перевіряємо, чи є в дочірньому списку будь-який елемент (тобто t2), який не міститься у батьківському списку (тобто t1). Якщо такого немає, то список є підмножиною іншого

наприклад:

bool isSubset = !(t2.Any(x => !t1.Contains(x)));

-1

Спробуйте це

static bool IsSubSet<A>(A[] set, A[] toCheck) {
  return set.Length == (toCheck.Intersect(set)).Count();
}

Ідея тут полягає в тому, що Intersect поверне лише ті значення, які є в обох масивах. У цей момент, якщо довжина отриманого набору така сама, як і вихідний набір, то всі елементи в "set" також знаходяться в "check" і тому "set" є підмножиною "toCheck"

Примітка. Моє рішення не працює, якщо "set" має дублікати. Я цього не змінюю, бо не хочу красти чужі голоси.

Підказка: Я проголосував за відповідь Кемерона.


4
Це працює, якщо вони справді є наборами, але не якщо другий "набір" містить повторювані елементи, оскільки це справді список. Ви можете скористатися HashSet <double>, щоб переконатися, що він встановив семантику.
tvanfosson

не працює, коли обидва масиви мають елементи, яких немає в іншому масиві.
da_berni
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.