C# 从两个列表(或数组)中匹配项

C# 从两个列表(或数组)中匹配项,c#,.net,arrays,list,C#,.net,Arrays,List,我在工作中遇到了一个问题,希望可以归结为以下几点:我有两个Lists,我想看看ListA中的ints是否等于ListB中的int。(它们可以是数组,如果这样可以简化操作,但我认为List有一些内置的魔法可能会有所帮助。)我确信这是一个LINQ友好的问题,但我在这里使用的是2.0 到目前为止,我的解决方案是通过ListA使用foreach,然后通过ListB使用foreach foreach (int a in ListA) { foreach (int b in ListB) {

我在工作中遇到了一个问题,希望可以归结为以下几点:我有两个
List
s,我想看看
ListA
中的
int
s是否等于
ListB
中的
int
。(它们可以是数组,如果这样可以简化操作,但我认为
List
有一些内置的魔法可能会有所帮助。)我确信这是一个LINQ友好的问题,但我在这里使用的是2.0

到目前为止,我的解决方案是通过ListA使用
foreach
,然后通过ListB使用
foreach

foreach (int a in ListA)
{
    foreach (int b in ListB)
    {
        if (a == b)
        {
            return true;
        }
    }
}
当它们都有三个项目长时,这实际上是非常巧妙的,但是现在它们有200个项目长,并且经常不匹配,所以我们得到了N^2比较的最坏情况。即使是40000个比较也进行得相当快,但我认为我可能遗漏了一些东西,因为对于这个特定的问题,N^2似乎非常幼稚


谢谢

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

将整个ListA加载到一个HashSet实例中,然后针对HastSet测试ListB中的每个项:我非常确定这将是O(N)

//前面有未测试的代码
HashSet HashSet=新的HashSet(ListA);
foreach(列表B中的int i)
{
if(hashSet.Contains(i))
返回true;
}
在一行中有同样的内容:

return new HashSet<int>(ListA).Overlaps(ListB);
返回新的HashSet(ListA)。重叠(ListB);

HashSet在.NET 3.5中不存在,因此在.NET 2.0中,您可以使用
字典
(而不是使用
HashSet
),并且始终将null作为对象/值存储在字典中,因为您只对键感兴趣。

与其遍历每个列表,不如看看该方法:

Chris通过散列给出了一个O(N)解。现在,根据常量因子(由于散列),可能需要考虑通过排序的O(N log(N))解决方案。根据您的用例,有几个不同的变体可以考虑。
  • 排序ListB(O(N log(N)),并使用搜索算法解析ListA中的每个元素(同样是O(N)*O(log(N)))

  • 对ListA和ListB进行排序(O(N log(N)),并使用O(N)算法来比较这些列表中的重复项

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

    对于,这很简单,因为您可以在上调用,以提供两个数组的集合交集:

    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 }
    
    然后,在扫描这两个列表时,您必须自己维护项目计数和产量/减量

    首先,您需要收集一个包含单个项目列表的列表:

    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);
        }
    }
    
    //匹配的项目。
    IList matched=新列表();
    //扫描
    foreach(列表b中的int b)
    {
    //伯爵。
    整数计数;
    //如果在a中找到该项。
    if(countsOfA.TryGetValue(b,out count))
    {
    //这是积极的。
    断言(计数>0);
    //将项目添加到列表中。
    (b)加入(b);
    //减少计数。如果
    //0,删除。
    如果(--count==0)countsOfA.移除(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);
            }
        }
    }
    
    公共静态IEnumerable MultisetIntersect(此IEnumerable首先,
    (可数秒)
    {
    //使用默认比较器调用重载。
    返回first.MultisetIntersect(第二个,EqualityComparer.Default);
    }
    公共静态IEnumerable MultiSetterSect(此IEnumerable优先,
    IEnumerable秒,IEqualityComparer比较器)
    {
    //验证参数。单独执行此操作,以便检查
    //立即执行,而不是在执行时执行
    //发生了。
    如果(first==null)抛出新的ArgumentNullException(“first”);
    如果(second==null)抛出新的ArgumentNullException(“second”);
    如果(comparer==null)抛出新的ArgumentNullException(“comparer”);
    //推迟执行内部命令
    //例如。
    返回第一个.MultisetIntersectImplementation(第二个,comparer);
    }
    私有静态IEnumerable MultiSetterSection实现(
    这个IEnumerable第一,IEnumerable第二,
    IEqualityComparer(比较器)
    {
    //验证参数。
    Assert(第一个!=null);
    Assert(第二个!=null);
    Assert(比较器!=null);
    //拿到第一本字典。
    IDictionary counts=first.GroupBy(t=>t,comparer)。
    ToDictionary(g=>g.Key,g.LongCount(),comparer);
    //扫描
    foreach(以秒计)
    {
    //伯爵。
    长计数;
    //如果在a中找到该项。
    if(计数TryGetValue(t,超出计数))
    {
    //这是积极的。
    断言(计数>0);
    //交出物品。
    收益率t;
    //减少计数。如果
    //0,删除。
    如果(--count==0)计数,则删除(t);
    }
    }
    }
    

    请注意,这两种方法都是(如果我在这里破坏Big-O表示法,我很抱歉)
    O(N+m)
    其中
    N
    是第一个数组中的项数,
    M
    是第二个数组中的项数。您只需扫描每个列表一次,并且假设获取哈希代码并查找哈希代码是一个
    O(1)
    (常数)操作。

    这并不比原来的解决方案好:仍然是O(N^2)教我在睡觉前发布…在更深入地研究Contains方法时,它确实执行列表的内部迭代。在这种情况下,Dictionary对象可能是最好的
    // 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);
            }
        }
    }