表达式优化

本文关键字:优化 表达式 | 更新日期: 2023-09-27 18:05:16

我有下面的代码,我的目标是获得随机结果集,其中有10个整数列表,每个列表包含4个整数,每个整数的相加超过80。

        var numList = new List<int> { 5, 20, 1, 7, 19, 3, 15, 60, 3, 21, 57, 9 };
        var extractedList = (from n1 in numList
                             from n2 in numList
                             from n3 in numList
                             from n4 in numList
                             where n1 + n2 + n3 + n4 > 80
                             select new { n1, n2, n3, n4 }).ToList();

现在我知道我可以很容易地添加"。GetRange (startIndex, 10)"到".ToList()",但在这个场景中提取的列表将有8799件,将添加"GetRange"意味着只有10项加载到内存,或者它将意味着8799年就被加载,然后过滤到10,我是一个LINQ新手所以我希望有一个有效的方法,也因为我知道没有数量将超过200,将使用一个8位字节帮助性能,任何建议这将是伟大的。

表达式优化

如果您只想要前10项,您可以使用:

var numList = new List {5,20,1,7,19,3,15,60,3,21,57,9};

    var extractedList = (from n1 in numList
                         from n2 in numList
                         from n3 in numList
                         from n4 in numList
                         where n1 + n2 + n3 + n4 > 80
                         select new { n1, n2, n3, n4 }).Take(10).ToList();

但是,它将始终生成相同的10项。

使用Take方法只加载特定数量的项目而不是所有项目,然后调用ToList

(from n1 in numList
 from n2 in numList
 from n3 in numList
 from n4 in numList
 where n1 + n2 + n3 + n4 > 80
 select new { n1, n2, n3, n4 }).Take(10).ToList();

正如已经在注释中指出的那样,这将总是给出相同的结果。您可以随机化列表,然后执行查询(而不是对数千个组合进行排序)

Random rnd = new Random();
numList = numList.OrderBy(x => rnd.Next()).ToArray();
(from n1 in numList
 from n2 in numList
 from n3 in numList
 from n4 in numList
 where n1 + n2 + n3 + n4 > 80
 select new { n1, n2, n3, n4 }).Take(10).ToList();

如果你想继续使用LINQ:

var numList = new List<int> { 5, 20, 1, 7, 19, 3, 15, 60, 3, 21, 57, 9 };
Random rnd = new Random();
var extractedList = (from n1 in numList
                     from n2 in numList
                     from n3 in numList
                     from n4 in numList
                     where n1 + n2 + n3 + n4 > 80
                     select new
                     {
                         n1,
                         n2,
                         n3,
                         n4,
                         Rnd = rnd.NextDouble()
                     })
                     .OrderBy(z => z.Rnd)
                     .Take(10)
                     .ToList();

请注意,我已经添加了一个随机参数,所以你不会总是得到相同的结果。

如果你在List上调用。getrange,你必须首先有一个List。因此,整个查询将被枚举,结果将在一个列表中,然后从该列表中取出10个项目。

如果你只想要这10个条目,你可以对查询本身使用Skip和Take。

var extractedList = (from n1 in numList
                     from n2 in numList
                     from n3 in numList
                     from n4 in numList
                     where n1 + n2 + n3 + n4 > 80
                     select new { n1, n2, n3, n4 })
                     .Skip(startIndex).Take(10);

但是,如果您想将结果分成长度为10的部分,那么每次都会枚举整个查询,因此您不希望这样做。在这种情况下,将整个结果存储在List中(就像您在示例中所做的那样)会更好。

使用Take LINQ表达式

var numList = new List<int> { 5, 20, 1, 7, 19, 3, 15, 60, 3, 21, 57, 9 };
    var extractedList = (from n1 in numList
                         from n2 in numList
                         from n3 in numList
                         from n4 in numList
                         where n1 + n2 + n3 + n4 > 80
                         select new { n1, n2, n3, n4 }).Take(10).ToList();

它将只接受通过选择标准的前10个结果。要从随机集合中获得选择,OrderBy(x => rand . next())会有所帮助,但这将对整个集合排序,因此这取决于您是否可以在不影响性能的情况下随机化集合(例如在后台)。

或者,您可以考虑编写自己的IList扩展,例如:

IList<T> GetRandomElements(this IList<T> me, int numElements)
{
    var copyOfMe = new List<T>(me);
    List<T> results = new List<T>();
    Random rnd = new Random();
    for(int i=0; i<numElements;i++)
    {
        if(copyOfMe.Count > 0)
        {
            int index = Random.Next(0,results.Count);
            results.Add(copyOfMe[index]);
            copyOfMe.Remove(index);
        }
    }
}

但这确实需要一个illist输入(用于索引)。

这里发布的其他解决方案的问题是,它们是O(n4),因为它们在进行过滤之前从列表中生成四个项目的所有可能组合。对于问题中给出的短列表,这可能没问题,但无法扩展。

例如,定义var numList = Enumerable.Range(10, 50).ToList();(因此列表中有50个项目)会导致这里的其他解决方案花费大约15秒;下面这个更有效的版本只需要几分之一秒。

技巧是定义一个生成器函数,Linq只调用完成查询所需的最小次数(尽管while (true)使它看起来像一个无限循环,但yield return使它一次返回一个值):
static IEnumerable<Tuple<int, int, int, int>> Generate(IList<int> list)
{
    int max = list.Count;
    Random rnd = new Random();
    while (true)
    {
        yield return new Tuple<int, int, int, int>(
            rnd.Next(max), rnd.Next(max), rnd.Next(max), rnd.Next(max));
    }
}

然后你可以用Linq来调用它,像这样:

var quickList = Generate(numList)
    .Where(t => t.Item1 + t.Item2 + t.Item3 + t.Item4 > 80)
    .Distinct().Take(10).ToList();