匹配两个列表(或数组)中的项

时间:2009-01-07 05:35:24

标签: c# .net arrays list

我的工作有问题,希望减少到以下内容:我有两个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比较的最坏情况。甚至40,000次比较也相当快,但我想我可能会遗漏一些东西,因为N ^ 2对于这个特殊问题似乎很天真。

谢谢!

5 个答案:

答案 0 :(得分:38)

使用LINQ,这是微不足道的,因为您可以调用Intersect extension method上的Enumerable class来为您提供两个数组的集合交集:

var intersection = ListA.Intersect(ListB);

但是,这是 set 交集,意味着如果ListAListB中没有唯一值,则不会获得任何副本。换句话说,如果您有以下内容:

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是第一个数组中的项目数,{{1是第二个数组中的项数。您必须只扫描每个列表一次,并且假设获取哈希码并对哈希码执行查找是M(常量)操作。

答案 1 :(得分:7)

将整个ListA加载到HashSet实例中,然后针对HastSet测试ListB中的foreach项:我很确定这将是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);

.NET 3.5中不存在HashSet,因此在.NET 2.0中,您可以使用Dictionary<int,object>(而不是使用HashSet<int>),并始终将null存储为字典中的对象/值你只对钥匙感兴趣。

答案 2 :(得分:3)

不要遍历每个列表,而是查看List.Contains方法:

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

答案 3 :(得分:2)

Chris通过散列提供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)算法比较这些重复项列表。

  3. 如果两个列表将被多次使用,则首选第二种方法。

答案 4 :(得分:0)

如何使用BinarySearch方法而不是迭代内部循环中的所有元素?