Сопоставление элементов из двух списков (или массивов)

У меня проблема с моей работой, которая, надеюсь, сводится к следующему: у меня есть два List<int>, и я хочу посмотреть, равно ли какой-либо из int в ListA любому int в ListB. (Они могут быть массивами, если это облегчает жизнь, но я думаю, что в List<> есть некоторая встроенная магия, которая может помочь.) И я уверен, что это проблема, дружественная к LINQ, но я работаю здесь над 2.0.

Мое решение до сих пор заключалось в foreach через ListA, а затем на foreach через ListB,

foreach (int a in ListA)
{
    foreach (int b in ListB)
    {
        if (a == b)
        {
            return true;
        }
    }
}

что было на самом деле довольно гладко, когда каждый из них состоял из трех элементов, но теперь их длина составляет 200, и они часто не совпадают, поэтому мы получаем наихудший случай сравнения N ^ 2. Даже 40000 сравнений проходят довольно быстро, но я думаю, что могу чего-то упустить, поскольку N ^ 2 кажется довольно наивным для этой конкретной проблемы.

Спасибо!

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
27
0
40 403
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Как насчет использования метода BinarySearch вместо перебора всех элементов внутреннего цикла?

Разве BinarySearch не полагается на сортировку списка? msdn.microsoft.com/en-us/library/w4e7fxsh.aspx

Fabrizio C. 07.01.2009 09:45

Загрузите весь ListA в экземпляр HashSet, а затем протестируйте элемент foreach в ListB против HastSet: я почти уверен, что это будет O (N).

//untested code ahead
HashSet<int> hashSet = new HashSet<int>(ListA);
foreach (int i in ListB)
{
    if (hashSet.Contains(i))
        return true;
}

Вот то же самое в одной строке:

return new HashSet<int>(ListA).Overlaps(ListB);

HashSet не существует в .NET 3.5, поэтому в .NET 2.0 вы можете использовать Dictionary<int,object> (вместо использования HashSet<int>) и всегда сохранять null как объект / значение в Словаре, поскольку вас интересуют только ключи.

Hashset не был представлен до .NET 3.5.

casperOne 07.01.2009 09:00

Хеширование вообще неплохая идея. При необходимости реализовать его несложно.

PolyThinker 07.01.2009 09:07

В этом случае, используя .Net 2.0, вы можете использовать Dictionary <int, object> вместо HashSet (и всегда сохранять null как объект / значение в Dictionary, поскольку вас интересуют только ключи).

ChrisW 07.01.2009 09:08

для меня это лучшее решение. Спасибо @ChrisW

TuanTDA 01.08.2020 17:50

Вместо того, чтобы перебирать каждый список, взгляните на метод List.Contains:

foreach (int a in ListA)
{
  if (ListB.Contains(a))
    return true;
}

Это не лучше исходного решения: все еще O (N ^ 2)

ChrisW 07.01.2009 08:58

Научи меня публиковать сообщения прямо перед сном ... если взглянуть на метод Contains более подробно, он действительно выполняет внутреннюю итерацию списка. В этом случае, вероятно, лучшим маршрутом будет объект Dictionary.

Metro Smurf 07.01.2009 09:06

Крис дает решение O (N) путем хеширования. Теперь, в зависимости от постоянного коэффициента (из-за хеширования), возможно, стоит рассмотреть решение O (N log (N)) путем сортировки. Есть несколько различных вариантов, которые вы можете рассмотреть в зависимости от вашего варианта использования.

  1. Отсортируйте ListB (O (N log (N)) и используйте алгоритм поиска для анализа каждого элемента в ListA (который снова равен O (N) * O (log (N))).

  2. Отсортируйте списки ListA и ListB (O (N log (N)) и используйте алгоритм O (N) для сравнения этих списков на наличие дубликатов.

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

Ответ принят как подходящий

С LINQ это тривиально, так как вы можете вызвать Intersect метод расширения на Enumerable класс, чтобы получить заданное пересечение двух массивов:

var intersection = ListA.Intersect(ListB);

Однако это пересечение набор, что означает, что если ListA и ListB не имеют уникальных значений, вы не получите никаких копий. Другими словами, если у вас есть следующее:

var ListA = new [] { 0, 0, 1, 2, 3 };
var ListB = new [] { 0, 0, 0, 2 };

Затем ListA.Intersect(ListB) производит:

{ 0, 2 }

Если вы ожидаете:

{ 0, 0, 2 }

Затем вам придется самостоятельно вести подсчет элементов и уменьшать / уменьшать при сканировании двух списков.

Во-первых, вы хотите собрать Dictionary<TKey, int> со списками отдельных элементов:

var countsOfA = ListA.GroupBy(i => i).ToDictionary(g => g.Key, g => g.Count());

Оттуда вы можете просканировать ListB и поместить его в список, когда вы встретите элемент в countsOfA:

// The items that match.
IList<int> matched = new List<int>();

// Scan 
foreach (int b in ListB)
{
    // The count.
    int count;

    // If the item is found in a.
    if (countsOfA.TryGetValue(b, out count))
    {
        // This is positive.
        Debug.Assert(count > 0);

        // Add the item to the list.
        matched.Add(b);

        // Decrement the count.  If
        // 0, remove.
        if (--count == 0) countsOfA.Remove(b);
    }
}

Вы можете обернуть это в метод расширения, который откладывает выполнение следующим образом:

public static IEnumerable<T> MultisetIntersect(this IEnumerable<T> first,
    IEnumerable<T> second)
{
    // Call the overload with the default comparer.
    return first.MultisetIntersect(second, EqualityComparer<T>.Default);
}

public static IEnumerable<T> MultisetIntersect(this IEnumerable<T> first,
    IEnumerable<T> second, IEqualityComparer<T> comparer)
{
    // Validate parameters.  Do this separately so check
    // is performed immediately, and not when execution
    // takes place.
    if (first == null) throw new ArgumentNullException("first");
    if (second == null) throw new ArgumentNullException("second");
    if (comparer == null) throw new ArgumentNullException("comparer");

    // Defer execution on the internal
    // instance.
    return first.MultisetIntersectImplementation(second, comparer);
}

private static IEnumerable<T> MultisetIntersectImplementation(
    this IEnumerable<T> first, IEnumerable<T> second, 
    IEqualityComparer<T> comparer)
{
    // Validate parameters.
    Debug.Assert(first != null);
    Debug.Assert(second != null);
    Debug.Assert(comparer != null);

    // Get the dictionary of the first.
    IDictionary<T, long> counts = first.GroupBy(t => t, comparer).
        ToDictionary(g => g.Key, g.LongCount(), comparer);

    // Scan 
    foreach (T t in second)
    {
        // The count.
        long count;

        // If the item is found in a.
        if (counts.TryGetValue(t, out count))
        {
            // This is positive.
            Debug.Assert(count > 0);

            // Yield the item.
            yield return t;

            // Decrement the count.  If
            // 0, remove.
            if (--count == 0) counts.Remove(t);
        }
    }
}

Обратите внимание, что оба этих подхода (и я прошу прощения, если я разделываю здесь нотацию Big-O) O(N + M), где N - это количество элементов в первом массиве, а M - это количество элементов во втором массиве. Вы должны сканировать каждый список только один раз, и предполагается, что получение хэш-кодов и выполнение поиска по хеш-кодам является операцией O(1) (постоянной).

Использует ли Enumerable.Intersect аналогичный подход?

palswim 27.11.2012 21:17

@palswim Немного, но не совсем. Я обновил свой ответ, чтобы отразить Intersect, и я обновлю его более подробным ответом, в котором немного указаны подсчеты.

casperOne 27.11.2012 21:29

@palswim Обновлен ответ, чтобы отразить использование Intersect, а также оправдать ожидания при использовании пересечений на множестве по сравнению с мультимножеством.

casperOne 27.11.2012 21:48

Другие вопросы по теме