Linq IEnumerable扩展方法-如何提高性能?
我编写了以下扩展方法,用于查找满足传递给它的谓词的连续的项序列。序列中连续项目的数量由参数“sequenceSize”确定 例如,我可能有一个整数的IEnumerable,我想找到10个大于100的连续值。此扩展方法将确定是否存在这样的序列 这种方法效果很好。但是,由于它必须执行的操作,如果IEnumerable中有相当数量的元素,则速度可能会很慢,因为它必须从第一个元素开始,查找满足谓词的连续值,然后转到第二个元素并执行相同的操作,等等 我正在寻找如何加快这一进程的建议。我尝试使用Asparell(),但没有效果Linq IEnumerable扩展方法-如何提高性能?,linq,Linq,我编写了以下扩展方法,用于查找满足传递给它的谓词的连续的项序列。序列中连续项目的数量由参数“sequenceSize”确定 例如,我可能有一个整数的IEnumerable,我想找到10个大于100的连续值。此扩展方法将确定是否存在这样的序列 这种方法效果很好。但是,由于它必须执行的操作,如果IEnumerable中有相当数量的元素,则速度可能会很慢,因为它必须从第一个元素开始,查找满足谓词的连续值,然后转到第二个元素并执行相同的操作,等等 我正在寻找如何加快这一进程的建议。我尝试使用Aspare
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);
}
}
公共静态IEnumerable FindSequenceConsecutive(此IEnumerable序列,
谓词,
int sequenceSize)
{
IEnumerable current=序列;
while(current.Count()>sequenceSize)
{
IEnumerable窗口=当前.Take(sequenceSize);
if(window.Where(x=>谓词(x)).Count()>=sequenceSize)
收益返回窗;
当前=当前。跳过(1);
}
}
此方法缓慢的最可能原因是重复调用.Count()
,它将立即枚举序列以确定元素的数量
您最好明确地测试标准并跟踪计数,而不是重复使用Where()
和Count()
通常,此方法会大量枚举序列。如果调用.ToList()
枚举一次序列,然后对生成的列表执行操作,可能会获得很好的加速效果。(请注意,如果希望在无限长序列上使用此方法,则此方法不起作用。)
更新:您正在测试>=sequenceSize
,即使window.Count()==sequenceSize
。换句话说,您只需要All()
:
不确定这会有多大帮助,但至少在语义上更清楚
<强>进一步编辑< /强>:考虑此方法:
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++;
}
}
public static IEnumerable FindSequenceConsecutive(此IEnumerable序列,谓词谓词,int sequenceSize)
{
List=sequence.ToList();
List matchList=List.Select(x=>predicate(x)).ToList();
int start=0;
int count=list.count;
while(开始+顺序大小x))
收益返回列表.GetRange(开始,sequenceSize);
启动++;
}
}
它对序列求值一次,然后划分一个必要的列表。我认为这样的方法可能适合您,因为您可以遍历列表一次,基本上保持一个连续的项目队列,通过谓词,根据需要清除(全部)和出列(一个)
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();
}
}
}
屈服
3,4,5
8,3,5
3,5,6
我相信这个解决方案将提供最好的性能,并且随着序列变大,扩展性会更好,因为它不分配任何额外的缓冲区(列表或队列),也不必将结果转换为列表或对结果缓冲区进行任何计数。另外,它只在序列上迭代一次
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);
}
}
}
公共静态IEnumerable FindSequenceConsecutive(此IEnumerable序列,
谓词谓词,int sequenceSize)
{
IEnumerable window=Enumerable.Repeat(默认值(T),0);
整数计数=0;
foreach(序列中的var项目)
{
if(谓语(项))
{
window=window.Concat(可枚举.重复(第1项));
计数++;
如果(计数==序列大小)
{
收益返回窗;
窗口=窗口。跳过(1);
计数--;
}
}
其他的
{
计数=0;
窗口=可枚举。重复(默认值(T),0);
}
}
}
+1。我不能说,所有的是否都会产生可测量的影响,但这至少意味着你没有击中序列中的每个元素。如果您有一长串不符合筛选谓词的元素,这可能是一个很大的优点。@dlev-我不知道您的解决方案是如何工作的。我正在寻找连续的值。您的“匹配列表”实现似乎无法找到连续的值;我已经测试过:)匹配列表本质上是对序列的每个成员调用谓词的结果的缓存matchList[i]==谓词(list[i])
@dlev-它确实有效,而且速度非常快。干得好!谢谢。这是一个很好的努力,但这错过了序列。如果您有5个连续项通过谓词([a,b,c,d,e]),并且正在寻找3的序列,您将得到[a,b,c],但不是[b,c,d]和[c,d,e]。其次,我不确定可伸缩性的说法,但我不能太挑剔,因为我绝不是专家。但是像Enumerable.Repeat这样的方法也会创建垃圾,创建并填充类。Linq不是搭便车。@Anthony你发表了我的评论!事实上,我对你的答案投了更高的票,我很肯定这是最好的选择。@Anthony Shoot,你说得对。需要添加Skip()
或其他内容。呜呜。我只要向后滚,吉姆就能处理它。:)@Anthony-修复了该错误,现在它与您的解决方案基本相同,但更详细:-(,你的更优雅。我对两者进行了性能测试,即使在非常大的序列中,它们也完全相同,而你的解决方案在原始序列中赢得了超过26K个元素的1ms!因此,我猜这将归结为如何呈现结果的偏好。在你的解决方案中,“窗口”已经在
3,4,5
8,3,5
3,5,6
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);
}
}
}