linq 提高获取不同项目的性能

siotufzp  于 2023-06-27  发布在  其他
关注(0)|答案(5)|浏览(121)

如何将下面的代码转换为LINQ。列表有时可以包含20k或30k项。因此,我正在寻找的东西,提高性能和运行速度更快。下面是我的代码:

if(list1 != null)
     {
       foreach (var item in list1)
       {
        if(!list2.Any( x => x.Name == item.Name && x.Number == item.Number))
        {
          list2.Add(item)
        }
       }
     }

我试着使用Parallel.ForEach,但它抛出了一个“集合被修改”错误。

pcrecxhr

pcrecxhr1#

您可以使用LINQ Distinct方法。它需要一个IEqualityComparer设置,但幸运的是MSDN示例已经编写了您需要的内容:

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

namespace ConsoleApplication1
{
    class Program
    {
        static Random rand = new Random();

        // see https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.distinct for Distinct()
        public class Product
        {
            public string Name { get; set; }
            public int Number { get; set; }
        }

        // Custom comparer for the Product class
        class ProductComparer : IEqualityComparer<Product>
        {
            // Products are equal if their names and product numbers are equal.
            public bool Equals(Product x, Product y)
            {

                //Check whether the compared objects reference the same data.
                if (Object.ReferenceEquals(x, y)) return true;

                //Check whether any of the compared objects is null.
                if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                    return false;

                //Check whether the products' properties are equal.
                return x.Number == y.Number && x.Name == y.Name;
            }

            // If Equals() returns true for a pair of objects 
            // then GetHashCode() must return the same value for these objects.

            public int GetHashCode(Product product)
            {
                //Check whether the object is null
                if (Object.ReferenceEquals(product, null)) return 0;

                //Get hash code for the Name field if it is not null.
                int hashProductName = product.Name == null ? 0 : product.Name.GetHashCode();

                //Get hash code for the Code field.
                int hashProductCode = product.Number.GetHashCode();

                //Calculate the hash code for the product.
                return hashProductName ^ hashProductCode;
            }

        }

        static string RandomLetter()
        {
            return (rand.Next((int)'A', (int)'Z' + 1)).ToString();
        }

        static List<Product> CreateTestData()
        {
            int nItems = 20000;
            List<Product> data = new List<Product>(nItems);
            for (int i = 1; i <= nItems; i++)
            {
                data.Add(new Product { Name = RandomLetter() + RandomLetter(), Number = i % 10 });
            }

            return data;
        }

        static void Main(string[] args)
        {
            var list1 = CreateTestData();
            Stopwatch sw = new Stopwatch();
            sw.Start();
            List<Product> noduplicates = list1.Distinct(new ProductComparer()).ToList();
            sw.Stop();
            Console.WriteLine($"x items: {list1.Count()} no duplicates: {noduplicates.Count()} Time: {sw.ElapsedMilliseconds} ms");

            List<Product> list2 = new List<Product>();
            if (list1 != null)
            {
                sw.Restart();
                foreach (var item in list1)
                {
                    if (!list2.Any(x => x.Name == item.Name && x.Number == item.Number))
                    {
                        list2.Add(item);
                    }
                }
                sw.Stop();
                Console.WriteLine($"x items: {list1.Count()} list2: {noduplicates.Count()} Time: {sw.ElapsedMilliseconds} ms");
            }

            Console.ReadLine();

        }
    }
}

样本输出:

x items: 20000 no duplicates: 6393 Time: 12 ms
x items: 20000 list2: 6393 Time: 4225 ms

如果您已经有一些数据,您可以使用Union方法,再次使用比较器。
注意:我的RandomLetter()函数没有做我想要的。但这就足够了。

vnjpjtjt

vnjpjtjt2#

20 - 30 k的物品不是那么多。您所需要的只是替换潜在的缓慢线性搜索

list2.Any(x => x.Name == item.Name && x.Number == item.Number)

具有快速查找数据结构。
最简单的方法是构建一个具有匿名类型的HashSet,其中包含NameNumber属性。为了做到这一点,您可以使用以下方便的自定义扩展方法:

public static class Extensions
{
    public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null)
    {
        return new HashSet<T>(source, comparer);
    }
}

代码会是这样的:

if (list1 != null)
{
    var keys = list2.Select(item => new { item.Name, item.Number }).ToHashSet();
    foreach (var item in list1)
    {
        var key = new { item.Name, item.Number };
        if (!keys.Contains(key))
        {
            list2.Add(item);
            keys.Add(key);
        }
    }
}

这不是LINQ,但也不需要这样,因为LINQ是用于查询的,而代码是用于修改的。

nwlqm0z1

nwlqm0z13#

您可以将list2设置为ConcurrentBag类型,并如下所示。我不是100%肯定它会像预期的那样工作。

public class Item
{
    public string Name { get; set; }
    public int Number { get; set; }
}
public void test()
{
    var list1 = new List<Item>(); // add items to list1 or maybe load from a database?

    var list2 = new ConcurrentBag<Item>();

    Parallel.ForEach(list1.ToList(), (item, state, arg3) =>
    {
        if (!list2.Any(x => x.Name == item.Name && x.Number == item.Number))
        {
            list2.Add(item);
        }

    });

}
von4xj4u

von4xj4u4#

按您的分类分组,从组中选择第一条记录并创建列表。

  • 如果您不需要提供现有值,则最快 *
var list2 = list1.GroupBy(i => new { i.Name, i.Number })
                 .Select(g=>g.First())
                 .ToList();
  • 如果您有现有值,则很简单(比下一个版本慢4倍)*

如果你的list2有预先存在的值,你可以这样做…

var keys = list2.ToList();
var toadd = list1.GroupBy(i => new { i.Name, i.Number })
                 .Where(g => !keys.Any(i => i.Name == g.Key.Name
                                         && i.Number == g.Key.Number))
                 .Select(g=>g.First());
list2.AddRange(toadd);
  • 如果需要使用现有值更新集合,则最快 *
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items)
{
    return new HashSet<T>(items);
}

var keys = list2.Select(i => new { i.Name, i.Number }).ToHashSet();
var toadd = list1.GroupBy(i => new { i.Name, i.Number })
                 .Where(g => !keys.Contains(g.Key))
                 .Select(g => g.First());
list2.AddRange(toadd);
uyto3xhc

uyto3xhc5#

我最近优化了对不可变项列表的Distinct调用,该列表具有相同的类型用作键-字符串和整数。我重载object.GetHashCode和预先计算的哈希代码,并在构造过程中存储到私有字段中。GetHashCode重载刚刚从该私有成员返回值。
这个修改使得Distinct调用速度可能快了几百倍,因为在内部Distinct迭代器使用了它的内部HashSet实现,该实现对GetHashCode进行了大量调用。
注意:如果重载GetHashCode,您需要重载object.Equals,以便Distinct内部迭代器使用它。
请记住,如果成员在项目对象生存期内可能发生变化,则不能使用这种哈希代码缓存,否则每次有任何字段用作键(即在Equals中使用)的变化。

相关问题