パフォーマンスの高いLINQソリューションは可能ですが、率直に言って非常に醜いです。説明に一致するサブシーケンス(2番目の述語に一致するアイテムが見つかったときに終了する述語に一致する一連の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;
});
最後のステップ(許容可能な長さの最初のグループを見つける)は簡単ですが、最後の落とし穴が1つあります。指定された条件を満たさないグループの1つを選択しないようにすることです。
var result = groups.Where(g => !rejectPredicate(g.First()))
.FirstOrDefault(g => g.Count() >= 5);
上記のすべては、ソースシーケンスのシングルパスで実現されます。
このコードは、ソースシーケンスも終了するアイテムのシーケンスを受け入れることに注意してください(つまり、満足するアイテムが見つかったrejectPredicate
が、データが不足したために終了しません)。これを望まない場合は、わずかな変更が必要になります。
実際の動作をご覧ください。