C#通过特定属性比较两个大型项目列表

时间:2019-03-13 09:16:10

标签: c# list performance linq c#-4.0

我有两个大的项目列表,它们的类看起来像这样(两个列表的类型相同):

public class Items
{
 public string ItemID { get; set; }
 public int QuantitySold { get; set; }
}


var oldList = new List<Items>(); // oldList

var newList = new List<Items>(); // new list

旧列表包含数据库中的项目,新列表代表从API中提取的项目;

两个列表都非常大,每个列表中有10000+个项目(共20000个)

我需要将newList中的项目与“ oldList”中的项目进行比较,看看哪些具有相同itemID值,具有不同“ QuantitySold”值的项目,以及具有不同“ QuantitySold”值的项目应存储在第三项中列表称为“ differentQuantityItems”。

我可以简单地做一遍foreach列表并比较值,但是由于两个列表都很大,因此使用double foreach循环的性能非常糟糕,我做不到...

有人可以帮我吗?

@YamamotoTetsua我已经在使用IEqualityComparer来获得所需的结果,但是它没有提供我期望的结果。这就是为什么...我有一个第一个IEqualityComparer,看起来像这样:

 public class MissingItemComparer : IEqualityComparer<SearchedUserItems>
    {
        public static readonly IEqualityComparer<SearchedUserItems> Instance = new MissingItemComparer();

        public bool Equals(SearchedUserItems x, SearchedUserItems y)
        {
            return x.ItemID == y.ItemID;
        }

        public int GetHashCode(SearchedUserItems x)
        {
            return x.ItemID.GetHashCode();
        }
    }

基本上,使用此IEqualityComparer可以从newList中获得数据库中不存在的项,如下所示:

var missingItems= newItems.Except(competitor.SearchedUserItems.ToList(), MissingItemComparer.Instance).ToList();

现在,在此列表中,我将获得来自API的新条目,但不在我的数据库中...

第二个IEqualityComparer基于旧列表和新列表中不同的QuantitySold:

public class ItemsComparer : IEqualityComparer<SearchedUserItems>
    {
        public static readonly IEqualityComparer<SearchedUserItems> Instance = new ItemsComparer();
        public bool Equals(SearchedUserItems x, SearchedUserItems y)
        {
            return (x.QuantitySold == y.QuantitySold);
        }
        public int GetHashCode(SearchedUserItems x)
        {
            return x.ItemID.GetHashCode();
        }
    }

用法示例:

var differentQuantityItems = newItems.Except(competitor.SearchedUserItems.ToList(), ItemsComparer.Instance).ToList();

这两个相等比较器的问题在于,例如第一个比较器将返回缺少的这些itemID:

123124124

123124421

512095902

它们确实从我的oldList中丢失了...但是,第二个IEQualityComparer也会将这些项目作为differentQuantity项目返回,它们确实存在,但是在oldList中不存在。因此,不应将它们包括在其中第二个列表。

4 个答案:

答案 0 :(得分:6)

这是LINQ Join的理想之选:

var differentQuantityItems =
    (from newItem in newList
     join oldItem in oldList on newItem.ItemID equals oldItem.ItemID
     where newItem.QuantitySold != oldItem.QuantitySold
     select newItem).ToList();

这将返回所有新项目,这些新项目具有对应的旧项目,且旧项目的数量不同。如果您还想包括新项目而没有相应的旧项目,请使用left outer join

var differentQuantityItems =
    (from newItem in newList
     join oldItem in oldList on newItem.ItemID equals oldItem.ItemID into oldItems
     from oldItem in oldItems.DefaultIfEmpty()
     where oldItem == null || newItem.QuantitySold != oldItem.QuantitySold
     select newItem).ToList();

在两种情况下,都使用join运算符快速将具有相同ItemID的项目相关联。然后,您可以比较QuantitySold或任何其他属性。

答案 1 :(得分:1)

从大O复杂度的角度来看,仅比较嵌套的for循环中的列表将属于 O(n * m),即 n 数据库中列表的大小,以及 m 从API获取的列表的大小。

您可以采取的措施是对两个列表进行排序,这将花费 O(n log (n)+ m log (m )),然后您可以在 O(n + m)中找到新项目。因此,算法的整体复杂度应为 O(n log (n)+ m log (m))

Here's an idea所需的时间,将二次解与超线性解进行比较。

答案 2 :(得分:1)

即使根本没有匹配项,该代码也将在不到一秒钟的时间内运行(如果一切是匹配项,也将在不到一秒钟的时间内运行)。

它将返回两个列表中存在的所有项目(即相同的ItemID),但具有不同的QuantitySold

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp5
{
    class Program
    {
        public class Items
        {
            public string ItemID { get; set; }
            public int QuantitySold { get; set; }
        }

        static void Main(string[] args)
        {
            // Sample data
            var oldList = new List<Items>();
            oldList.AddRange(Enumerable.Range(0, 20000).Select(z => new Items() { ItemID = z.ToString(), QuantitySold = 4 }));

            var newList = new List<Items>();
            newList.AddRange(Enumerable.Range(0, 20000).Select(z => new Items() { ItemID = z.ToString(), QuantitySold = 5 }));

            var results = oldList.Join(newList,
                                            left => left.ItemID,
                                            right => right.ItemID,
                                            (left, right) => new { left, right })
                                .Where(z => z.left.QuantitySold != z.right.QuantitySold).Select(z => z.left);

            Console.WriteLine(results.Count());
            Console.ReadLine();
        }
    }
}

使用z.left意味着将仅返回一个项-如果您希望同时使用新旧项目,请使用:

var results = oldList.Join(newList,
                                left => left.ItemID,
                                right => right.ItemID,
                                (left, right) => new { left, right })
                    .Where(z => z.left.QuantitySold != z.right.QuantitySold)
                    .Select(z => new[] { z.left, z.right })
                    .SelectMany(z => z);

答案 3 :(得分:0)

您可以考虑将Except子句与自定义的IEqualityComparer一起使用,如下所示

var oldList = new List<Item>(); // oldList
var newList = new List<Item>(); // new list
var distinctList = newList.Except(oldList,new ItemEqualityComparer()).ToList();



class ItemEqualityComparer : IEqualityComparer<Item>
        {
            public bool Equals(Item i1, Item i2)
            {
                if (i1.ItemID == i2.ItemID && i1.QuantitySold != i2.QuantitySold)
                    return false;
                return true;
            }

            public int GetHashCode(Item item)
            {
                return item.ItemID.GetHashCode();
            }
        }

public class Item
        {
            public string ItemID { get; set; }
            public int QuantitySold { get; set; }
        }