是否有一个 LINQ 替代运算符来代替 Where() 运算符,其中包含 List.Contains() 方法
本文关键字:运算符 包含 List 方法 Contains LINQ Where 是否 有一个 | 更新日期: 2023-09-27 18:31:44
使用 LINQ,是否有更快的替代谓词内有List<T>.Contains()
的 Where()
方法,给出完全相同的结果?
这是一个例子:
List<int> a = ...
List<int> b = ...
var result = a.Where(x => b.Contains(x)); //very slow
我发现的一种替代方法是使用Intersect()
方法:
var result = a.Intersect(b);
result
变量中,a
保留值顺序。但是,如果 a
中的值包含重复项,则它不会提供完全相同的结果Intersect()
因为运算符仅返回不同的值。
另一种方式:
var result = a.Join(b, x => x, y => y, (x, y) => x);
同样,如果b
包含重复项,则结果也不相同。
还有别的可能吗?
我想避免的:
- 创建我自己的 LINQ 扩展方法
- 在第一个列表上创建单独的
HashSet
,并在内部使用Contains()
Where()
。
从语义上讲,你想要的是一个左内部连接。 LINQ Join
运算符执行内部联接,该联接很接近,但不完全相同。 幸运的是,您可以使用GroupJoin
执行左联接。
var query = from n in a
join k in b
on n equals k into matches
where matches.Any()
select n;
另一种选择是将第二个序列中的项目放入一个可以比List
更有效地搜索的HashSet
中。 (这类似于 Join/GroupJoin 将在内部执行的操作。
var set = new HashSet<int>(b);
var query = a.Where(n => set.Contains(n));
另一种选择是使用 Join
,就像您所做的那样,但只需先从b
中删除所有重复项,因为如果没有重复项,那么它会执行您想要的操作:
var result = a.Join(b.Distinct(), x => x, y => y, (x, y) => x);
对于更快和重复,我会使用传统的"for"。
编辑
我编写了一个测试代码,考虑:
- 包含 1000 个随机整数的列表。
- 每种方法进行200次测试。
- 结果的 1、2、4 和 8 使用
IEnumerable<int>
来显示需要将 LINQ 的结果转换为更好的数据结构,如List<int>
(如果多次使用结果)。
结果是这些:
1 uses per result
Tigrou-Where : count= 93, 3.167,0ms
Tigrou-Intersect : count= 89, 116,0ms
Tigrou-Join : count= 96, 179,0ms
Servy-GroupJoin : count= 93, 262,0ms
Servy-HashSet : count= 93, 71,0ms
Servy-JoinDisctinct : count= 93, 212,0ms
JoseH-TheOldFor : count= 93, 72,0ms
2 uses per result
Tigrou-Where : count= 93, 6.007,0ms
Tigrou-Intersect : count= 89, 182,0ms
Tigrou-Join : count= 96, 293,0ms
Servy-GroupJoin : count= 93, 455,0ms
Servy-HashSet : count= 93, 99,0ms
Servy-JoinDisctinct : count= 93, 407,0ms
JoseH-TheOldFor : count= 93, 73,0ms
4 uses per result
Tigrou-Where : count= 93, 11.866,0ms
Tigrou-Intersect : count= 89, 353,0ms
Tigrou-Join : count= 96, 565,0ms
Servy-GroupJoin : count= 93, 899,0ms
Servy-HashSet : count= 93, 165,0ms
Servy-JoinDisctinct : count= 93, 786,0ms
JoseH-TheOldFor : count= 93, 73,0ms
8 uses per result
Tigrou-Where : count= 93, 23.831,0ms
Tigrou-Intersect : count= 89, 724,0ms
Tigrou-Join : count= 96, 1.151,0ms
Servy-GroupJoin : count= 93, 1.807,0ms
Servy-HashSet : count= 93, 299,0ms
Servy-JoinDisctinct : count= 93, 1.570,0ms
JoseH-TheOldFor : count= 93, 81,0ms
代码是:
class Program
{
static void Main(string[] args)
{
Random random = new Random(Environment.TickCount);
var cases = 1000;
List<int> a = new List<int>(cases);
List<int> b = new List<int>(cases);
for (int c = 0; c < cases; c++)
{
a.Add(random.Next(9999));
b.Add(random.Next(9999));
}
var times = 100;
var usesCount = 1;
Console.WriteLine("{0} times", times);
for (int u = 0; u < 4; u++)
{
Console.WriteLine();
Console.WriteLine("{0} uses per result", usesCount);
TestMethod(a, b, "Tigrou-Where", Where, times, usesCount);
TestMethod(a, b, "Tigrou-Intersect", Intersect, times, usesCount);
TestMethod(a, b, "Tigrou-Join", Join, times, usesCount);
TestMethod(a, b, "Servy-GroupJoin", GroupJoin, times, usesCount);
TestMethod(a, b, "Servy-HashSet", HashSet, times, usesCount);
TestMethod(a, b, "Servy-JoinDisctinct", JoinDistinct, times, usesCount);
TestMethod(a, b, "JoseH-TheOldFor", TheOldFor, times, usesCount);
usesCount *= 2;
}
Console.ReadLine();
}
private static void TestMethod(List<int> a, List<int> b, string name, Func<List<int>, List<int>, IEnumerable<int>> method, int times, int usesCount)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
int count = 0;
for (int t = 0; t < times; t++)
{
// Process
var result = method(a, b);
// Count
for (int u = 0; u < usesCount; u++)
{
count = 0;
foreach (var item in result)
{
count++;
}
}
}
stopwatch.Stop();
Console.WriteLine("{0,-20}: count={1,4}, {2,8:N1}ms",
name, count, stopwatch.ElapsedMilliseconds);
}
private static IEnumerable<int> Where(List<int> a, List<int> b)
{
return a.Where(x => b.Contains(x));
}
private static IEnumerable<int> Intersect(List<int> a, List<int> b)
{
return a.Intersect(b);
}
private static IEnumerable<int> Join(List<int> a, List<int> b)
{
return a.Join(b, x => x, y => y, (x, y) => x);
}
private static IEnumerable<int> GroupJoin(List<int> a, List<int> b)
{
return from n in a
join k in b
on n equals k into matches
where matches.Any()
select n;
}
private static IEnumerable<int> HashSet(List<int> a, List<int> b)
{
var set = new HashSet<int>(b);
return a.Where(n => set.Contains(n));
}
private static IEnumerable<int> JoinDistinct(List<int> a, List<int> b)
{
return a.Join(b.Distinct(), x => x, y => y, (x, y) => x);
}
private static IEnumerable<int> TheOldFor(List<int> a, List<int> b)
{
var result = new List<int>();
int countA = a.Count;
var setB = new HashSet<int>(b);
for (int loopA = 0; loopA < countA; loopA++)
{
var itemA = a[loopA];
if (setB.Contains(itemA))
result.Add(itemA);
}
return result;
}
}
在使用之前更改代码中的一行以将结果转换为List<int>
,这些用途为 8 种:
8 uses per result
Tigrou-Where : count= 97, 2.974,0ms
Tigrou-Intersect : count= 91, 91,0ms
Tigrou-Join : count= 105, 150,0ms
Servy-GroupJoin : count= 97, 224,0ms
Servy-HashSet : count= 97, 74,0ms
Servy-JoinDisctinct : count= 97, 223,0ms
JoseH-TheOldFor : count= 97, 75,0ms
所以,我认为获胜者是:Servy-HashSet 方法,带有一点变体:
var set = new HashSet<int>(b);
var result = a.Where(n => set.Contains(n)).ToList();