.NET Generics - 比较两个列表和过滤器:最佳实践

时间:2011-06-19 22:03:41

标签: c# .net vb.net list generics

我有两个类型为T的通用列表。两个列表包含相同的类型,我想根据List中不存在的列表2中的项目创建第三个列表(或列表2的过滤版本) 1,基于每个项目的ID。

每个列表都包含一个“Package”对象,该对象具有ID属性。

现在我使用For Each循环模拟代码,我知道这很糟糕(Big O是恒定的时间)所以我想要一种更有效的方法。

这个代码是按照项目的要求在VB中进行的,但我更喜欢C# - 所以任何一个代码示例都适用于我。

Private Sub RemoveStockPackagesFromSelection()

    Dim p As Package
    Dim packageList As List(Of Package) = New List(Of Package)
    Dim stockPackageList As List(Of Package) = New List(Of Package)
    Dim result As List(Of Package) = New List(Of Package)

    ' Fill list with User's Packages
    For i As Integer = 0 To ListBox2.Items.Count - 1
        p = New Package
        p.Id = CInt(ListBox2.Items(i).Value)
        p.Name = ListBox2.Items(i).Text
        packageList.Add(p)
    Next

    ' Fill list with Stock Packages to compare:
    Dim ds As DataSet = DAL.GetStandardPackages()

    For Each dr As DataRow In ds.Tables(0).Rows
        p = New Package
        p.Id = CInt(dr.Item("id"))
        stockPackageList.Add(p)
    Next

    ' Do Compare and Filter
    For Each p1 As Package In packageList
        For Each p2 As Package In stockPackageList
            If Not p1.Id = p2.Id Then
                result.Add(p2)
            End If
        Next
    Next

    ' Here is our new trimmed list:
    Response.Write(result.Count)

End Sub

这种过滤的LINQ或Lamda方式有什么好看的?什么是我的方法的大O和拟议方法的大O(只是为了满足我的好奇心)。

由于

5 个答案:

答案 0 :(得分:8)

LINQ除方法

这是最干净的方式,正如Maxim和svick所建议的那样,但需要一个等于ID的重写Equals方法,或者你必须提供一个比较器(参见svicks答案)。

var result = stockPackageList.Except(packageList).ToList();

<强>资源 许多LINQ samles可以在http://msdn.microsoft.com/en-us/vcsharp/aa336746

的msdn中找到

我会留下答案的初始部分作为参考:

蛮力方式:

var result = stockPackageList
              .Where(x => packageList.All(package => x.Id != package.Id))
              .ToList();

应该做的伎俩。您只需将lambda语法翻译为vb.net。

此查询将过滤stockPackageListpackageList所有项目中不存在ID的所有项目。

您可以反转查询:

var result = stockPackageList
              .Where(x => packageList.Any(package => x.Id == package.Id) == false)
              .ToList();

如果Any中的任何项目具有匹配的ID,则packageList查询将返回true。此查询应该运行得更快一些,因为它不必遍历整个集合,因为All必须这样做。

使用Eqality:

如果您的包对象实现IEquatable<Package>,您可以将代码缩短为

var result = stockPackageList
              .Where(x => packageList.Contains(x) == false)
              .ToList();

使用哈希集:

如果你想使用哈希集,你可以做

var hash = new HashSet<string>(packageList.Select(x=>x.Id));
var result = stockPackageList.Where(x => hash.Contains(x.Id) == false).ToList();

这可以节省列表变大的计算时间,就像费斯特和伊万丹尼洛夫指出的那样。

答案 1 :(得分:7)

无法真正读懂VB代码,但如果你想让l2中的项目不在l1中 -

她是一个C#代码样本

   public class SomeObject
    {
        public string ID { get; set; }
    }

    public class SomeObjectComparer : IEqualityComparer<SomeObject>
    {
        public bool Equals(SomeObject x, SomeObject y)
        {
            return x.ID == y.ID;
        }

        public int GetHashCode(SomeObject obj)
        {
            return obj.ID.GetHashCode();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<SomeObject> l1, l2;
            // lists init ...

            IEqualityComparer<SomeObject> comparer = new SomeObjectComparer();

            List<SomeObject> l3 = l2.Except(l1, comparer).ToList();

        }
    }

答案 2 :(得分:2)

您的方法具有O(m * n)的渐近运行时间,其中m和n是集合的大小。

你应该争取O(m lg n)。您当然需要搜索两个集合,但是您可以在O(n)中构建其中一个集合的散列集,并且平均在O(1)中执行查询,因此您应该将一个列表复制到散列集并遍历第二个集合前者的价值观。

    static void Sort()
    {
        List<Package> a = new List<Package>();
        List<Package> b = new List<Package>();

        Func<Package, int> idExtractor = x => x.ID;

        var hash = new HashSet<Package>(a, new IDComparer<Package, int>(idExtractor));

        a.AddRange(b.Where(x => !hash.Contains(x)));
    }

    class IDComparer<ObjectType, KeyType>
        : IEqualityComparer<ObjectType>
        where KeyType : IComparable
    {
        private Func<ObjectType, KeyType> idExtractor;

        public IDComparer(Func<ObjectType, KeyType> idExtractor)
        {
            this.idExtractor = idExtractor;
        }

        public bool Equals(ObjectType x, ObjectType y)
        {
            return idExtractor(x).Equals(idExtractor(y));
        }

        public int GetHashCode(ObjectType obj)
        {
            return idExtractor(obj).GetHashCode();
        }
    }

答案 3 :(得分:1)

您可以使用Except()

result = stockpackageList.Except(PackageList).ToList();

这假定Package已超载Equals()以比较Id。如果没有,则必须使用IEqualityComparer,例如来自this answer的那个:

result = stockpackageList.Except(PackageList, 
                                 new KeyEqualityComparer<Package, int>(p => p.Id))
                         .ToList();

至于时间复杂性,您的解决方案无法正常工作,因此复杂性无关紧要。 Except()的复杂性是O(N + M),因为它首先从第一个集合(O(N))创建一个哈希集,然后尝试从第二个集合中删除每个项目(O(M))然后返回结果。

答案 4 :(得分:0)

var dic = new Dictionary<string, Package>(p1.Count); // capacity here is important
foreach (var p in p1) 
    dic.Add(p.Id, p);
var result = p2.Where(p => !dic.ContainsKey(p.Id)).ToList();

由p1.Count填写字典几乎 O(n)。每次搜索都是O(1)。所以我们有一些接近线性复杂性的东西。