我编写了以下扩展方法,该方法查找满足传递给它的谓词的连续项目序列。序列中连续项的数量由参数'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);
}
}
答案 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);
}
}
}