あなたは可能性が使用してクエリの数を使用Take
し、Skip
、それが元のリストにあまりにも多くの反復を追加し、私は信じています。
むしろ、次のように独自のイテレータを作成する必要があると思います。
public static IEnumerable<IEnumerable<T>> GetEnumerableOfEnumerables<T>(
IEnumerable<T> enumerable, int groupSize)
{
// The list to return.
List<T> list = new List<T>(groupSize);
// Cycle through all of the items.
foreach (T item in enumerable)
{
// Add the item.
list.Add(item);
// If the list has the number of elements, return that.
if (list.Count == groupSize)
{
// Return the list.
yield return list;
// Set the list to a new list.
list = new List<T>(groupSize);
}
}
// Return the remainder if there is any,
if (list.Count != 0)
{
// Return the list.
yield return list;
}
}
これを呼び出すと、LINQが有効になり、結果のシーケンスに対して他の操作を実行できます。
サムの答えに照らして、これなしでこれを行う簡単な方法があると感じました:
- リストを繰り返し処理する(元々は行っていません)
- チャンクを解放する前にアイテムをグループ化する(アイテムの大きなチャンクの場合、メモリの問題が発生します)
- サムが投稿したすべてのコード
とはいえ、ここで別のパスを示します。これは、拡張メソッドでコード化してIEnumerable<T>
呼び出されChunk
ます。
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source,
int chunkSize)
{
// Validate parameters.
if (source == null) throw new ArgumentNullException("source");
if (chunkSize <= 0) throw new ArgumentOutOfRangeException("chunkSize",
"The chunkSize parameter must be a positive value.");
// Call the internal implementation.
return source.ChunkInternal(chunkSize);
}
そこまで驚くことではなく、基本的なエラーチェックだけです。
に移動ChunkInternal
:
private static IEnumerable<IEnumerable<T>> ChunkInternal<T>(
this IEnumerable<T> source, int chunkSize)
{
// Validate parameters.
Debug.Assert(source != null);
Debug.Assert(chunkSize > 0);
// Get the enumerator. Dispose of when done.
using (IEnumerator<T> enumerator = source.GetEnumerator())
do
{
// Move to the next element. If there's nothing left
// then get out.
if (!enumerator.MoveNext()) yield break;
// Return the chunked sequence.
yield return ChunkSequence(enumerator, chunkSize);
} while (true);
}
基本的には、 IEnumerator<T>
各項目を手動で反復します。現在列挙されているアイテムがあるかどうかを確認します。各チャンクが列挙された後、アイテムが残っていない場合はブレークアウトします。
シーケンスにアイテムがあることを検出すると、内部IEnumerable<T>
実装の責任を次のように委任しChunkSequence
ます。
private static IEnumerable<T> ChunkSequence<T>(IEnumerator<T> enumerator,
int chunkSize)
{
// Validate parameters.
Debug.Assert(enumerator != null);
Debug.Assert(chunkSize > 0);
// The count.
int count = 0;
// There is at least one item. Yield and then continue.
do
{
// Yield the item.
yield return enumerator.Current;
} while (++count < chunkSize && enumerator.MoveNext());
}
以来MoveNext
、すでに上で呼ばれていたIEnumerator<T>
に渡されChunkSequence
、それがで返されるアイテムを生み出しますCurrent
以上返すようにしてください決してなっていないし、次にカウントをインクリメントchunkSize
項目とすべての反復後シーケンスの次の項目に移動する(ただし、数の場合は短絡します生成されたアイテムがチャンクサイズを超えています)。
アイテムが残っていない場合、InternalChunk
メソッドは外側のループで別のパスを作成しますがMoveNext
、2回目に呼び出されても、ドキュメントに従って falseを返します。(強調鉱山)のとおり。
MoveNextがコレクションの最後を渡すと、列挙子はコレクションの最後の要素の後に配置され、MoveNextはfalseを返します。列挙子がこの位置にある場合、MoveNextへの後続の呼び出しも、Resetが呼び出されるまでfalseを返します。
この時点で、ループが壊れ、シーケンスのシーケンスが終了します。
これは簡単なテストです:
static void Main()
{
string s = "agewpsqfxyimc";
int count = 0;
// Group by three.
foreach (IEnumerable<char> g in s.Chunk(3))
{
// Print out the group.
Console.Write("Group: {0} - ", ++count);
// Print the items.
foreach (char c in g)
{
// Print the item.
Console.Write(c + ", ");
}
// Finish the line.
Console.WriteLine();
}
}
出力:
Group: 1 - a, g, e,
Group: 2 - w, p, s,
Group: 3 - q, f, x,
Group: 4 - y, i, m,
Group: 5 - c,
重要なノートでは、これはなりません、あなたが親配列の任意の時点で全体の子配列またはブレークを排出していない場合は動作します。これは重要な注意点ですが、あなたのユースケースは、あなたが消費するということであれば、すべてを、シーケンスのシーケンスの要素を、これはうまくいきます。
さらに、Samが一時点で行ったように、注文をいじると奇妙なことが起こります。