Linq IEnumerable扩展方法 - 如何提高性能?

时间:2011-08-25 19:54:35

标签: linq

我编写了以下扩展方法,该方法查找满足传递给它的谓词的连续项目序列。序列中连续项的数量由参数'sequenceSize。

确定

作为一个例子,我可能有一个IEnumerable的整数,我想找到10个大于100的连续值。这个扩展方法将确定是否存在这样的序列。

此方法效果很好。但是,由于它必须做的事情,如果IEnumerable中有相当数量的元素,它可能会很慢,因为它必须从第一个元素开始,查找满足谓词的连续值,然后转到第二个元素并执行同样的等等。

我正在寻找有关如何提高速度的建议。我尝试使用AsParallel(),但没有影响。

public static IEnumerable<IEnumerable<T>> FindSequenceConsecutive<T>(this IEnumerable<T> sequence, 
                                                                     Predicate<T> predicate, 
                                                                     int sequenceSize)
{
    IEnumerable<T> current = sequence;

    while (current.Count() > sequenceSize)
    {
        IEnumerable<T> window = current.Take(sequenceSize);

        if (window.Where(x => predicate(x)).Count() >= sequenceSize)
            yield return window;

        current = current.Skip(1);
    }
}

3 个答案:

答案 0 :(得分:5)

此方法缓慢的最可能原因是重复调用.Count()会立即枚举序列以确定元素的数量。

您最好明确测试标准并跟踪计数,而不是反复使用Where()Count()

通常,此方法枚举序列很多。如果您调用.ToList()枚举序列一次,然后在生成的列表上执行操作,则可能会获得良好的加速。 (请注意,如果您希望在无限长度序列上使用此方法,则此方法无效。)

更新:您正在测试>= sequenceSize,即使是window.Count() == sequenceSize。换句话说,您只需要All()

if (window.All(x => predicate(x)))
    yield return window;

不确定这会有多大帮助,但至少在语义上更清晰。

进一步修改:请考虑以下方法:

public static IEnumerable<IEnumerable<T>> FindSequenceConsecutive<T>(this IEnumerable<T> sequence, Predicate<T> predicate, int sequenceSize)
{
    List<T> list = sequence.ToList();
    List<bool> matchList = list.Select(x => predicate(x)).ToList();

    int start = 0;
    int count = list.Count;

    while (start + sequenceSize <= count)
    {
        var range = matchList.GetRange(start, sequenceSize);
        if (range.All(x => x))
            yield return list.GetRange(start, sequenceSize);

        start++;
    }
}

它会对序列进行一次评估,然后对必要的列表进行分区。

答案 1 :(得分:4)

我认为这样的事情可能对你有用,因为你可以在列表上走一次并且基本上维护一个连续项目的队列,传递谓词,清除(全部)和出列(一个)必要。

public static IEnumerable<IEnumerable<T>> FindSequenceConsecutive<T>(this IEnumerable<T> sequence, Predicate<T> predicate, int sequenceSize)
{
    var queue = new Queue<T>();

    foreach (T item in sequence)
    {
        if (predicate(item))
        {
            queue.Enqueue(item);
            if (queue.Count == sequenceSize)
            {
                yield return queue.ToList();
                queue.Dequeue();
            }
        }
        else
        {
            queue.Clear();
        }
    }
}

所以写作

int[] array = { 1, 2, 3, 4, 5, 2, 8, 3, 5, 6 };
foreach (var seq in array.FindSequenceConsecutive(i => i > 2, 3))
{
    Console.WriteLine(string.Join(",", seq));
}

收益率

3,4,5
8,3,5
3,5,6

答案 2 :(得分:3)

我相信这个解决方案将提供最佳性能,并且随着序列变大而更好地扩展,因为它不会分配任何额外的缓冲区(列表或队列),也不必将结果转换为List或执行任何操作计算结果缓冲区。另外,它只迭代序列一次。

public static IEnumerable<IEnumerable<T>> FindSequenceConsecutive<T>(this IEnumerable<T> sequence,
    Predicate<T> predicate, int sequenceSize)
{
    IEnumerable<T> window = Enumerable.Repeat(default(T), 0);

    int count = 0;

    foreach (var item in sequence)
    {
        if (predicate(item))
        {
            window = window.Concat(Enumerable.Repeat(item, 1));
            count++;

            if (count == sequenceSize)
            {
                yield return window;
                window = window.Skip(1);
                count--;
            }
        }
        else
        {
            count = 0;
            window = Enumerable.Repeat(default(T), 0);
        }                
    }
}