是否有一个 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 替代运算符来代替 Where() 运算符,其中包含 List.Contains() 方法

从语义上讲,你想要的是一个左内部连接。 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();