リストを一連の小さなリストに分割しようとしています。
私の問題:リストを分割する私の機能は、それらを正しいサイズのリストに分割しません。サイズ30のリストに分割する必要がありますが、サイズ114のリストに分割しますか?
関数でリストをサイズ以下のX個のリストに分割するにはどうすればよいですか?
public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30)
{
List<List<float[]>> list = new List<List<float[]>>();
for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) {
List <float[]> subLocat = new List <float[]>(locations);
if (subLocat.Count >= ((i*nSize)+nSize))
subLocat.RemoveRange(i*nSize, nSize);
else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize));
Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString());
list.Add (subLocat);
}
return list;
}
サイズ144のリストで関数を使用すると、出力は次のようになります。
インデックス:4、サイズ:120
インデックス:3、サイズ:114
インデックス:2、サイズ:114
インデックス:1、サイズ:114
インデックス:0、サイズ:114
public static List<List<float[]>> splitList(List<float[]> locations, int nSize=30)
{
var list = new List<List<float[]>>();
for (int i=0; i < locations.Count; i+= nSize)
{
list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i)));
}
return list;
}
汎用バージョン:
public static IEnumerable<List<T>> splitList<T>(List<T> locations, int nSize=30)
{
for (int i=0; i < locations.Count; i+= nSize)
{
yield return locations.GetRange(i, Math.Min(nSize, locations.Count - i));
}
}
この拡張メソッドを使用して、指定されたチャンクサイズでソースリストをサブリストにチャンクすることをお勧めします。
/// <summary>
/// Helper methods for the lists.
/// </summary>
public static class ListExtensions
{
public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize)
{
return source
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / chunkSize)
.Select(x => x.Select(v => v.Value).ToList())
.ToList();
}
}
たとえば、18アイテムのリストをチャンクごとに5アイテムずつチャンクすると、次のアイテムを含む4つのサブリストのリストが表示されます:5-5-5-3。
どうですか:
while(locations.Any())
{
list.Add(locations.Take(nSize).ToList());
locations= locations.Skip(nSize).ToList();
}
Serj-Tmソリューションは問題ありません。また、これはリストの拡張メソッドとしての汎用バージョンです(静的クラスに入れます)。
public static List<List<T>> Split<T>(this List<T> items, int sliceSize = 30)
{
List<List<T>> list = new List<List<T>>();
for (int i = 0; i < items.Count; i += sliceSize)
list.Add(items.GetRange(i, Math.Min(sliceSize, items.Count - i)));
return list;
}
受け入れられた回答(Serj-Tm)が最も堅牢であることがわかりましたが、一般的なバージョンを提案したいと思います。
public static List<List<T>> splitList<T>(List<T> locations, int nSize = 30)
{
var list = new List<List<T>>();
for (int i = 0; i < locations.Count; i += nSize)
{
list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i)));
}
return list;
}
私はフロートを含む任意のタイプを取る一般的なメソッドを持っています、それはユニットテストされています、それが役立つことを願っています:
/// <summary>
/// Breaks the list into groups with each group containing no more than the specified group size
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="groupSize">Size of the group.</param>
/// <returns></returns>
public static List<List<T>> SplitList<T>(IEnumerable<T> values, int groupSize, int? maxCount = null)
{
List<List<T>> result = new List<List<T>>();
// Quick and special scenario
if (values.Count() <= groupSize)
{
result.Add(values.ToList());
}
else
{
List<T> valueList = values.ToList();
int startIndex = 0;
int count = valueList.Count;
int elementCount = 0;
while (startIndex < count && (!maxCount.HasValue || (maxCount.HasValue && startIndex < maxCount)))
{
elementCount = (startIndex + groupSize > count) ? count - startIndex : groupSize;
result.Add(valueList.GetRange(startIndex, elementCount));
startIndex += elementCount;
}
}
return result;
}
ライブラリMoreLinqにはBatch
というメソッドがあります
List<int> ids = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; // 10 elements
int counter = 1;
foreach(var batch in ids.Batch(2))
{
foreach(var eachId in batch)
{
Console.WriteLine("Batch: {0}, Id: {1}", counter, eachId);
}
counter++;
}
結果は
Batch: 1, Id: 1
Batch: 1, Id: 2
Batch: 2, Id: 3
Batch: 2, Id: 4
Batch: 3, Id: 5
Batch: 3, Id: 6
Batch: 4, Id: 7
Batch: 4, Id: 8
Batch: 5, Id: 9
Batch: 5, Id: 0
ids
は、2つの要素を持つ5つのチャンクに分割されます。
上記の答えの多くは仕事をしますが、それらはすべて終わらないシーケンス(または本当に長いシーケンス)で恐ろしく失敗します。以下は、可能な限り最適な時間とメモリの複雑さを保証する完全にオンラインの実装です。列挙可能なソースを1回だけ反復し、遅延評価にyield returnを使用します。消費者は、繰り返しごとにリストを破棄して、batchSize
要素の数を含むリストのメモリフットプリントと等しくすることができます。
public static IEnumerable<List<T>> BatchBy<T>(this IEnumerable<T> enumerable, int batchSize)
{
using (var enumerator = enumerable.GetEnumerator())
{
List<T> list = null;
while (enumerator.MoveNext())
{
if (list == null)
{
list = new List<T> {enumerator.Current};
}
else if (list.Count < batchSize)
{
list.Add(enumerator.Current);
}
else
{
yield return list;
list = new List<T> {enumerator.Current};
}
}
if (list?.Count > 0)
{
yield return list;
}
}
}
編集:OPがList<T>
をより小さなList<T>
に分割することを要求することに気付いたので、無限列挙型に関する私のコメントはOPには適用されませんが、ここで終わる他の人を助けるかもしれません。これらのコメントは、関数への入力としてIEnumerable<T>
を使用するが、列挙可能なソースを複数回列挙する他の投稿されたソリューションへの応答でした。
最後にmhandの非常に有用なコメントの後の追加
ほとんどのソリューションは動作する可能性がありますが、私はそれらが非常に効率的ではないと思います。最初の数個のチャンクの最初の数個のアイテムのみが必要な場合を考えます。次に、シーケンス内のすべての(数十億)アイテムを反復処理する必要はありません。
以下は、最大で2回列挙します。1回はテイク用、1回はスキップ用です。使用する以上の要素は列挙しません。
public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>
(this IEnumerable<TSource> source, int chunkSize)
{
while (source.Any()) // while there are elements left
{ // still something to chunk:
yield return source.Take(chunkSize); // return a chunk of chunkSize
source = source.Skip(chunkSize); // skip the returned chunk
}
}
ソースをchunkSize
のチャンクに分割するとします。最初のN個のチャンクのみを列挙します。列挙されたすべてのチャンクから、最初のM要素のみを列挙します。
While(source.Any())
{
...
}
anyはEnumeratorを取得し、1 MoveNext()を実行し、Enumeratorを破棄した後に戻り値を返します。これはN回行われます
yield return source.Take(chunkSize);
参照元 によると、これは次のようなことをします。
public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
return TakeIterator<TSource>(source, count);
}
static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
foreach (TSource element in source)
{
yield return element;
if (--count == 0) break;
}
}
これは、フェッチされたChunkの列挙を開始するまで、あまり役に立ちません。複数のチャンクをフェッチしたが、最初のチャンクを列挙しないことを決定した場合、デバッガーが示すように、foreachは実行されません。
最初のチャンクの最初のM個の要素を取得する場合、yield returnは正確にM回実行されます。これの意味は:
最初のチャンクがyieldで返された後、この最初のチャンクをスキップします。
source = source.Skip(chunkSize);
もう一度: 参照ソース を見てskipiterator
を見つけます
static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
while (count > 0 && e.MoveNext()) count--;
if (count <= 0)
{
while (e.MoveNext()) yield return e.Current;
}
}
}
ご覧のとおり、SkipIterator
は、チャンク内のすべての要素に対してMoveNext()
を1回呼び出します。 Current
を呼び出しません。
したがって、チャンクごとに次のことが行われていることがわかります。
取る():
コンテンツが列挙されている場合:GetEnumerator()、列挙されたアイテムごとに1つのMoveNextと1つのCurrent、Dispose列挙子。
Skip():列挙されるすべてのチャンク(チャンクの内容ではありません):GetEnumerator()、MoveNext()chunkSize times、no Current!列挙子を破棄
列挙子で何が起こるかを見ると、MoveNext()の呼び出しが多くあり、実際にアクセスすることに決めたTSourceアイテムのCurrent
の呼び出しだけがあります。
サイズがchunkSizeのN個のチャンクを使用する場合、MoveNext()の呼び出し
フェッチされたすべてのチャンクの最初のM要素のみを列挙する場合、列挙されたチャンクごとにM回MoveNextを呼び出す必要があります。
合計
MoveNext calls: N + N*M + N*chunkSize
Current calls: N*M; (only the items you really access)
したがって、すべてのチャンクのすべての要素を列挙することにした場合:
MoveNext: numberOfChunks + all elements + all elements = about twice the sequence
Current: every item is accessed exactly once
MoveNextが大量の作業であるかどうかは、ソースシーケンスの種類によって異なります。リストと配列の場合、範囲外のチェックを伴う可能性がある、単純なインデックスインクリメントです。
ただし、IEnumerableがデータベースクエリの結果である場合は、データが実際にコンピューター上で実体化されていることを確認してください。そうでない場合、データは数回フェッチされます。 DbContextおよびDapperは、アクセスする前にデータをローカルプロセスに適切に転送します。同じシーケンスを数回列挙しても、数回フェッチされません。 DapperはListであるオブジェクトを返します。DbContextはデータが既にフェッチされていることを記憶しています。
チャンク内のアイテムの分割を開始する前にAsEnumerable()またはToLists()を呼び出すのが賢明かどうかは、リポジトリによって異なります
public static IEnumerable<IEnumerable<T>> SplitIntoSets<T>
(this IEnumerable<T> source, int itemsPerSet)
{
var sourceList = source as List<T> ?? source.ToList();
for (var index = 0; index < sourceList.Count; index += itemsPerSet)
{
yield return sourceList.Skip(index).Take(itemsPerSet);
}
}
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems)
{
return items.Select((item, index) => new { item, index })
.GroupBy(x => x.index / maxItems)
.Select(g => g.Select(x => x.item));
}
これはどう?アイデアは、1つのループのみを使用することでした。そして、ご存知のように、コード全体でIList実装のみを使用しており、Listにキャストしたくない場合があります。
private IEnumerable<IList<T>> SplitList<T>(IList<T> list, int totalChunks)
{
IList<T> auxList = new List<T>();
int totalItems = list.Count();
if (totalChunks <= 0)
{
yield return auxList;
}
else
{
for (int i = 0; i < totalItems; i++)
{
auxList.Add(list[i]);
if ((i + 1) % totalChunks == 0)
{
yield return auxList;
auxList = new List<T>();
}
else if (i == totalItems - 1)
{
yield return auxList;
}
}
}
}
public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize)
{
var result = new List<List<T>>();
for (int i = 0; i < source.Count; i += chunkSize)
{
var rows = new List<T>();
for (int j = i; j < i + chunkSize; j++)
{
if (j >= source.Count) break;
rows.Add(source[j]);
}
result.Add(rows);
}
return result;
}
もう一つ
public static IList<IList<T>> SplitList<T>(this IList<T> list, int chunkSize)
{
var chunks = new List<IList<T>>();
List<T> chunk = null;
for (var i = 0; i < list.Count; i++)
{
if (i % chunkSize == 0)
{
chunk = new List<T>(chunkSize);
chunks.Add(chunk);
}
chunk.Add(list[i]);
}
return chunks;
}