如何在linq中优化FirstOrDefault语句

时间:2015-08-14 16:31:59

标签: c# algorithm performance linq

我有两个linq语句,第一个占用25毫秒,第二个占用1100秒,循环100,000。

我已经用ElementAt替换了FirstAll,甚至使用foreach来获取第一个元素,但仍然需要相同的时间。 有没有更快的方法来获得第一个元素?

我考虑过其他一些问题,但仍无法找到解决此问题的任何解决方案。

var matches = (from subset in MyExtensions.SubSetsOf(List1)
                where subset.Sum() <= target
                select subset).OrderByDescending(i => i.Sum());
var match = matches.FirstOrDefault(0);

也尝试过:

foreach (var match in matches)
{
    break;
}

甚至:

var match = matches.ElementAt(0);

任何意见都将不胜感激。

编辑:这是SubSetOf的代码

public static class MyExtensions
{
    public static IEnumerable<IEnumerable<T>> SubSetsOf<T>(this IEnumerable<T> source)
    {
        // Deal with the case of an empty source (simply return an enumerable containing a single, empty enumerable)
        if (!source.Any())
            return Enumerable.Repeat(Enumerable.Empty<T>(), 1);

        // Grab the first element off of the list
        var element = source.Take(1);

        // Recurse, to get all subsets of the source, ignoring the first item
        var haveNots = SubSetsOf(source.Skip(1));

        // Get all those subsets and add the element we removed to them
        var haves = haveNots.Select(set => element.Concat(set));

        // Finally combine the subsets that didn't include the first item, with those that did.
        return haves.Concat(haveNots);
    }

}

3 个答案:

答案 0 :(得分:5)

你两次打电话给Sum--这很糟糕。预先知道:

        var matches = MyExtensions.SubSetsOf(List1)
                        .Select(subset => new { subset, Sum = subset.Sum() })
                        .Where(o => o.Sum < target).OrderByDescending(i => i.Sum);
        var match = matches.FirstOrDefault();
        var subset = match != null ? match.subset : null;

答案 1 :(得分:3)

正如Jason所说,它是subset sum problem - Knapsack Problem的选项,其中weight等于value。最简单的解决方案 - 生成所有子集并检查它们的总和,但这种算法具有可怕的复杂性。所以,我们的优化并不重要。

你应该使用动态programmig来解决这个问题:

假设二维数组D(i, c) - i元素的最大总和小于或等于cN - 是元素的数量(列表大小)。 W - 最大总和(您的目标)。 每个D(0,c) = 0 c,因为你没有元素:) 将c1更改为W并将i1更改为N让我们计算 D(i,c) = max(D(i-1,c),D(i-1,c-list[i])+list[i])

要恢复子集,我们必须存储父节点数组并在计算期间设置它们。 另一个例子是here。 整个代码:

class Program
{
    static void Main(string[] args)
    {
        var list = new[] { 11, 2, 4, 6 };
        var target = 13;

        var n = list.Length;
        var result = KnapSack(target, list, n);
        foreach (var item in result)
        {
            Console.Write(item + " ");
        }
    }

    private static List<int> KnapSack(int target, int[] val, int n)
    {
        var d = new int[n + 1, target + 1];
        var p = new int[n + 1, target + 1];
        for (var i = 0; i <= n; i++)
        {
            for (var c = 0; c <= target; c++)
            {
                p[i, c] = -1;
            }
        }

        for (int i = 0; i <= n; i++)
        {
            for (int c = 0; c <= target; c++)
            {
                if (i == 0 || c == 0)
                {
                    d[i, c] = 0;
                }
                else
                {
                    var a = d[i - 1, c];
                    if (val[i - 1] <= c)
                    {
                        var b = val[i - 1] + d[i - 1, c - val[i - 1]];
                        if (a > b)
                        {
                            d[i, c] = a;
                            p[i, c] = p[i - 1, c];
                        }
                        else
                        {
                            d[i, c] = b;
                            p[i, c] = i - 1;
                        }
                    }
                    else
                    {
                        d[i, c] = a;
                        p[i, c] = p[i - 1, c];
                    }
                }
            }
        }

        //sum
        //Console.WriteLine(d[n, target);

        //restore set
        var resultSet = new List<int>();
        var m = n;
        var s = d[n, target];
        var t = p[m, s];
        while (t != -1)
        {
            var item = val[t];
            resultSet.Add(item);
            m--;
            s -= item;
            t = p[m, s];
        }

        return resultSet;
    }
}

答案 2 :(得分:2)

看起来您正在尝试解决的一般问题是找到最大总和小于target的数字子集。 linq函数的执行时间是解决方案的症状。这是一个众所周知且研究得很多的问题,称为“knapsack problem”。我相信你的特定变体将属于“有界背包问题”类,其重量等于该值。我将从研究开始。您实施的解决方案,强制每个可能的子集,被称为“天真”解决方案。我很确定它是所有可能解决方案中表现最差的。