4

LINQを学習しようとしていますが、述語に一致する一連の「n」要素を見つけることは可能であると思われますが、問題へのアプローチ方法がわかりません。

私のソリューションでは、シーケンスの「終了」をテストするために2番目の異なる述語が実際に必要ですが、テストに合格した少なくとも5つの要素のシーケンスの後で、テストを通過しない最初の要素を見つけること興味深いでしょう。

これが私の素朴な非LINQアプローチです...

 int numPassed = 0;
 for (int i = 0; i < array.Count - 1; i++ )
 {
     if (FirstTest(array[i]))
     {
        numPassed++;
     }
     else
     {
        numPassed = 0;
     }

     if ((numPassed > 5) && SecondTest(array[i + 1]))
     {
          foundindex = i;
          break;
     }
  }
4

6 に答える 6

3

パフォーマンスの高い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が、データが不足したために終了しません)。これを望まない場合は、わずかな変更が必要になります。

実際の動作をご覧ください

于 2012-08-03T08:31:58.723 に答える
1

既存の拡張メソッドを組み合わせようとする代わりに、Enumerator.


例:

IEnumerable<T> MatchThis<T>(IEnumerable<T> source, 
                            Func<T, bool> first_predicate,
                            Int32 times_match,
                            Func<T, bool> second_predicate)
{
    var found = new List<T>();
    using (var en = source.GetEnumerator())
    {
        while(en.MoveNext() && found.Count < times_match)
            if (first_predicate((T)en.Current))
                found.Add((T)en.Current);
            else
                found.Clear();

        if (found.Count < times_match && !en.MoveNext() || !second_predicate((T)en.Current))
            return Enumerable.Empty<T>();

        found.Add((T)en.Current);
        return found;
    }
}

使用法:

var valid_seq = new Int32[] {800, 3423, 423423, 1, 2, 3, 4, 5, 200, 433, 32};
var result = MatchThis(valid_seq, e => e<100, 5, e => e>100);

結果:

ここに画像の説明を入力

于 2012-08-03T08:43:14.853 に答える
1

エレガントではありませんが、これは機能します:

var indexList = array
                 .Select((x, i) => new 
                     { Item = x, Index = i })
                 .Where(item => 
                     item.Index + 5 < array.Length && 
                     FirstTest(array[item.Index]) && 
                     FirstTest(array[item.Index+1]) && 
                     FirstTest(array[item.Index+2]) && 
                     FirstTest(array[item.Index+3]) && 
                     FirstTest(array[item.Index+4]) && 
                     SecondTest(array[item.Index+5]))
                 .Select(item => item.Index);
于 2012-08-03T08:15:46.623 に答える
0

連続した6つの要素が必要なようです。最初の5つは述語1に一致し、最後(6番目)は述語2に一致します。非linqバージョンは正常に機能します。この場合、linqを使用するのは少し気が進まないです。そして、1つのlinqクエリで問題を解決しようとすると、問題が難しくなります。これが(おそらく)よりクリーンなlinqソリューションです。

int continuous = 5;
var temp = array.Select(n => FirstTest(n) ? 1 : 0);
var result = array.Where((n, index) => 
               index >= continuous 
               && SecondTest(n) 
               && temp.Skip(index - continuous).Take(continuous).Sum() == continuous)
    .FirstOrDefault();

方法があれば物事は簡単になりますMorelinq.Batch

于 2012-08-03T08:46:29.770 に答える
0
var result = array.GetSixth(FirstTest).FirstOrDefault(SecondTest);

internal static class MyExtensions
{
      internal static IEnumerable<T> GetSixth<T>(this IEnumerable<T> source, Func<T, bool> predicate)
       {
            var counter=0;
            foreach (var item in source)
            {
                if (counter==5) yield return item;
                counter = predicate(item) ? counter + 1 : 0;
            }
        }
}
于 2012-08-03T08:53:20.800 に答える
-1

他の人が述べたように、LINQ はこの種のパターン マッチングのニーズに対する理想的なソリューションではありません。それでも、それは可能であり、醜い必要はありません:

Func<int, bool> isBody = n => n == 8;
Func<int, bool> isEnd = n => n == 2;
var requiredBodyLength = 5;

//    Index:       0  1  2  3  4  5  6  7  8  9 10 11 12 13 14
int[] sequence = { 6, 8, 8, 9, 2, 1, 8, 8, 8, 8, 8, 8, 8, 2, 5 };
//                                         ^-----------^  ^
//                                             Body      End

// First we stick an index on each element, since that's the desired output.
var indexedSequence = sequence.Select((n, i) => new { Index = i, Number = n }).ToArray();

// Scroll to the right to see comments
var patternMatchIndexes = indexedSequence
    .Select(x => indexedSequence.Skip(x.Index).TakeWhile(x2 => isBody(x2.Number)))             // Skip all the elements we already processed and try to match the body
    .Where(body => body.Count() == requiredBodyLength)                                         // Filter out any body sequences of incorrect length
    .Select(body => new { BodyIndex = body.First().Index, EndIndex = body.Last().Index + 1 })  // Prepare the index of the first body element and the index of the end element
    .Where(x => x.EndIndex < sequence.Length && isEnd(sequence[x.EndIndex]))                   // Make sure there is at least one element after the body and that it's an end element
    .Select(x => x.BodyIndex)                                                                  // There may be more than one matching pattern, get all their indices
    .ToArray();

//patternMatchIndexes.Dump();   // Uncomment in LINQPad to see results

この実装はまったくパフォーマンスが高くないことに注意してください。LINQ で何かを解決する方法が不適切であるにもかかわらず、LINQ で何かを実行する方法を示すための教材としてのみ意図されています

于 2012-08-03T08:53:12.467 に答える