一个高性能的 LINQ 解决方案是可能的,但坦率地说相当难看。这个想法是隔离与描述匹配的子序列(一系列 N 项与谓词匹配,当找到与第二个谓词匹配的项时结束),然后选择其中具有最小长度的第一个。
假设参数是:
var data = new[] { 0, 1, 1, 1, 0, 0, 2, 2, 2, 2, 2 };
Func<int, bool> acceptPredicate = i => i != 0;
// The reverse of acceptPredicate, but could be otherwise
Func<int, bool> rejectPredicate = i => i == 0;
GroupBy
使用一堆丑陋的有状态代码可以隔离子序列(这是固有的尴尬——你必须保持非平凡的状态)。这个想法是通过一个人为的和任意的“组号”进行分组,每当我们从一个可能可以接受的子序列移动到一个绝对不可接受的子序列时选择一个不同的数字,并且当相反的情况发生时:
var acceptMode = false;
var groupCount = 0;
var groups = data.GroupBy(i => {
if (acceptMode && rejectPredicate(i)) {
acceptMode = false;
++groupCount;
}
else if (!acceptMode && acceptPredicate(i)) {
acceptMode = true;
++groupCount;
}
return groupCount;
});
最后一步(找到可接受长度的第一组)很容易,但还有最后一个陷阱:确保您不选择不满足规定条件的组:
var result = groups.Where(g => !rejectPredicate(g.First()))
.FirstOrDefault(g => g.Count() >= 5);
以上所有这些都是通过源序列上的单次传递来实现的。
请注意,此代码将接受也结束源序列的项目序列(即它不会终止,因为我们找到了一个满足的项目,rejectPredicate
而是因为我们用完了数据)。如果您不希望这样做,则需要稍作修改。
看到它在行动。