如何将以下代码转换为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,但它会抛出一个" Collection已被修改"错误。
答案 0 :(得分:1)
您可以使用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://msdn.microsoft.com/en-us/library/bb338049(v=vs.110).aspx 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
方法,再次使用比较器。
N.B。我的RandomLetter()
功能不符合我的意图。但它就足够了。
答案 1 :(得分:1)
20 - 30k项目不是那么多。您所需要的只是替换潜在的慢线性搜索
list2.Any(x => x.Name == item.Name && x.Number == item.Number)
具有快速查找数据结构。
最简单的方法是使用包含HashSet
和Name
属性的匿名类型构建Number
。为此,您可以使用以下方便的自定义扩展方法:
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用于查询,而您的代码是用于修改。
答案 2 :(得分:0)
你可以使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);
}
});
}
答案 3 :(得分:0)
按照您的干扰进行分组,然后从该组中选择第一条记录并创建您的列表。
如果您不需要提供现有值,则最快
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);