最初にすべきことは、リストを並べ替えることだと思います。次に、現在のシーケンスの長さを記憶し、いつ終了したかを検出して、それをウォークスルーするだけです。正直なところ、単純な foreach ループが最も簡単な方法になるのではないかと思います。LINQのように素晴らしくきちんとした方法をすぐには思いつきません。本当にやりたいのであれば、確かにイテレータブロックでそれを行うことができますが、最初にリストを並べ替えるということは、とにかく合理的な「事前」コストがあることを意味することに注意してください。したがって、私のソリューションは次のようになります。
var ordered = list.OrderBy(x => x);
int count = 0;
int firstItem = 0; // Irrelevant to start with
foreach (int x in ordered)
{
// First value in the ordered list: start of a sequence
if (count == 0)
{
firstItem = x;
count = 1;
}
// Skip duplicate values
else if (x == firstItem + count - 1)
{
// No need to do anything
}
// New value contributes to sequence
else if (x == firstItem + count)
{
count++;
}
// End of one sequence, start of another
else
{
if (count >= 3)
{
Console.WriteLine("Found sequence of length {0} starting at {1}",
count, firstItem);
}
count = 1;
firstItem = x;
}
}
if (count >= 3)
{
Console.WriteLine("Found sequence of length {0} starting at {1}",
count, firstItem);
}
編集:さて、私は物事を行うためのかなりLINQっぽい方法を考えました。今は完全に実装する時間はありませんが、
- シーケンスを注文する
SelectWithPrevious
(おそらく)のようなものを使用してSelectConsecutive
、要素の連続したペアを取得します
- インデックスを含む Select のオーバーロードを使用して、(index、current、previous) のタプルを取得します。
- (current = previous + 1) のアイテムを除外して、シーケンスの開始として数えられる場所を取得します (特殊なケースのインデックス = 0)
SelectWithPrevious
結果に対して使用して、 2 つの開始点間のシーケンスの長さを取得します (前のインデックスから 1 つのインデックスを減算します)。
- 長さが 3 未満のシーケンスを除外します
最終的なアイテムが適切に使用されることを保証するために、順序付けられたシーケンスを連結する必要があると思います。int.MinValue
編集:さて、私はこれを実装しました。これは、これを行うために私が考えることができるLINQiest方法についてです...開始シーケンスと終了シーケンスを強制するために、null値を「センチネル」値として使用しました-詳細についてはコメントを参照してください。
全体として、このソリューションはお勧めしません。頭を丸めるのは難しいです。私はそれが正しいとかなり確信していますが、可能性のあるオフバイワンエラーなどを考えるのにしばらく時間がかかりました.LINQで何ができるかについての興味深い航海です...そしてまたおそらくすべきではないこと。
ああ、「最小長3」の部分を呼び出し元にプッシュしたことに注意してください-このようなタプルのシーケンスがある場合、個別にフィルターで除外する方がきれいです.IMO.
using System;
using System.Collections.Generic;
using System.Linq;
static class Extensions
{
public static IEnumerable<TResult> SelectConsecutive<TSource, TResult>
(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
using (IEnumerator<TSource> iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
yield break;
}
TSource prev = iterator.Current;
while (iterator.MoveNext())
{
TSource current = iterator.Current;
yield return selector(prev, current);
prev = current;
}
}
}
}
class Test
{
static void Main()
{
var list = new List<int> { 21,4,7,9,12,22,17,8,2,20,23 };
foreach (var sequence in FindSequences(list).Where(x => x.Item1 >= 3))
{
Console.WriteLine("Found sequence of length {0} starting at {1}",
sequence.Item1, sequence.Item2);
}
}
private static readonly int?[] End = { null };
// Each tuple in the returned sequence is (length, first element)
public static IEnumerable<Tuple<int, int>> FindSequences
(IEnumerable<int> input)
{
// Use null values at the start and end of the ordered sequence
// so that the first pair always starts a new sequence starting
// with the lowest actual element, and the final pair always
// starts a new one starting with null. That "sequence at the end"
// is used to compute the length of the *real* final element.
return End.Concat(input.OrderBy(x => x)
.Select(x => (int?) x))
.Concat(End)
// Work out consecutive pairs of items
.SelectConsecutive((x, y) => Tuple.Create(x, y))
// Remove duplicates
.Where(z => z.Item1 != z.Item2)
// Keep the index so we can tell sequence length
.Select((z, index) => new { z, index })
// Find sequence starting points
.Where(both => both.z.Item2 != both.z.Item1 + 1)
.SelectConsecutive((start1, start2) =>
Tuple.Create(start2.index - start1.index,
start1.z.Item2.Value));
}
}