web-dev-qa-db-ja.com

LINQを使用してリストをサブリストに分割する

各分割の区切り文字としてアイテムインデックスを使用して、List<SomeObject>SomeObjectのいくつかの個別のリストに分割する方法はありますか。

例証しましょう:

私はList<SomeObject>を持っています、そして私はList<List<SomeObject>>List<SomeObject>[]が必要です、それでこれらの結果として生じるリストの各々がオリジナルのリストの3つの項目のグループを(順番に)含むように。

例えば。:

  • 元のリスト:[a, g, e, w, p, s, q, f, x, y, i, m, c]

  • 結果のリスト:[a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]

この関数のパラメータには、結果のリストサイズも必要です。

352
Felipe Lima

次のコードを試してください。

public static IList<IList<T>> Split<T>(IList<T> source)
{
    return  source
        .Select((x, i) => new { Index = i, Value = x })
        .GroupBy(x => x.Index / 3)
        .Select(x => x.Select(v => v.Value).ToList())
        .ToList();
}

考えは最初にインデックスによって要素をグループ化することです。 3で除算すると、それらを3のグループにグループ化する効果があります。その後、各グループをリストに、IEnumerableListListsのListに変換します。

346
JaredPar

この質問は少し古いですが、私はこれを書いただけです、そして私はそれが他の提案された解決策よりもう少しエレガントであると思います:

/// <summary>
/// Break a list of items into chunks of a specific size
/// </summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
    while (source.Any())
    {
        yield return source.Take(chunksize);
        source = source.Skip(chunksize);
    }
}
301
CaseyB

一般的に、 CaseyB によって提案されたアプローチはうまくいきます。実際、あなたがList<T>を渡しているのであれば、それを失敗させるのは難しいです、おそらく私はそれを次のように変えるでしょう:

public static IEnumerable<IEnumerable<T>> ChunkTrivialBetter<T>(this IEnumerable<T> source, int chunksize)
{
   var pos = 0; 
   while (source.Skip(pos).Any())
   {
      yield return source.Skip(pos).Take(chunksize);
      pos += chunksize;
   }
}

これは大規模なコールチェーンを回避します。それにもかかわらず、このアプローチには一般的な問題があります。チャンクごとに2つの列挙型を具体化して、実行してみる問題を強調します。

foreach (var item in Enumerable.Range(1, int.MaxValue).Chunk(8).Skip(100000).First())
{
   Console.WriteLine(item);
}
// wait forever 

これを克服するために、 Cameron's アプローチを試すことができます。これは列挙を一度だけ歩くので上記のテストを空飛ぶ色で渡します。

問題はそれが異なる欠陥を持っているということです、それはそれぞれの塊の中のすべてのアイテムを具体化します、そのアプローチに関する問題はあなたがメモリを使い果たしているということです。

それを説明するために、実行してみてください。

foreach (var item in Enumerable.Range(1, int.MaxValue)
               .Select(x => x + new string('x', 100000))
               .Clump(10000).Skip(100).First())
{
   Console.Write('.');
}
// OutOfMemoryException

最後に、どのような実装でもチャンクの順番外の反復を処理できるはずです。例えば:

Enumerable.Range(1,3).Chunk(2).Reverse().ToArray()
// should return [3],[1,2]

私の最初の 改訂 この回答のように非常に最適な解決策はそこでは失敗しました。同じ問題が casperOneの最適化 answerでも見られます。

これらすべての問題に対処するには、次のようにします。

namespace ChunkedEnumerator
{
    public static class Extensions 
    {
        class ChunkedEnumerable<T> : IEnumerable<T>
        {
            class ChildEnumerator : IEnumerator<T>
            {
                ChunkedEnumerable<T> parent;
                int position;
                bool done = false;
                T current;


                public ChildEnumerator(ChunkedEnumerable<T> parent)
                {
                    this.parent = parent;
                    position = -1;
                    parent.wrapper.AddRef();
                }

                public T Current
                {
                    get
                    {
                        if (position == -1 || done)
                        {
                            throw new InvalidOperationException();
                        }
                        return current;

                    }
                }

                public void Dispose()
                {
                    if (!done)
                    {
                        done = true;
                        parent.wrapper.RemoveRef();
                    }
                }

                object System.Collections.IEnumerator.Current
                {
                    get { return Current; }
                }

                public bool MoveNext()
                {
                    position++;

                    if (position + 1 > parent.chunkSize)
                    {
                        done = true;
                    }

                    if (!done)
                    {
                        done = !parent.wrapper.Get(position + parent.start, out current);
                    }

                    return !done;

                }

                public void Reset()
                {
                    // per http://msdn.Microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
                    throw new NotSupportedException();
                }
            }

            EnumeratorWrapper<T> wrapper;
            int chunkSize;
            int start;

            public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
            {
                this.wrapper = wrapper;
                this.chunkSize = chunkSize;
                this.start = start;
            }

            public IEnumerator<T> GetEnumerator()
            {
                return new ChildEnumerator(this);
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

        }

        class EnumeratorWrapper<T>
        {
            public EnumeratorWrapper (IEnumerable<T> source)
            {
                SourceEumerable = source;
            }
            IEnumerable<T> SourceEumerable {get; set;}

            Enumeration currentEnumeration;

            class Enumeration
            {
                public IEnumerator<T> Source { get; set; }
                public int Position { get; set; }
                public bool AtEnd { get; set; }
            }

            public bool Get(int pos, out T item) 
            {

                if (currentEnumeration != null && currentEnumeration.Position > pos)
                {
                    currentEnumeration.Source.Dispose();
                    currentEnumeration = null;
                }

                if (currentEnumeration == null)
                {
                    currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
                }

                item = default(T);
                if (currentEnumeration.AtEnd)
                {
                    return false;
                }

                while(currentEnumeration.Position < pos) 
                {
                    currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
                    currentEnumeration.Position++;

                    if (currentEnumeration.AtEnd) 
                    {
                        return false;
                    }

                }

                item = currentEnumeration.Source.Current;

                return true;
            }

            int refs = 0;

            // needed for dispose semantics 
            public void AddRef()
            {
                refs++;
            }

            public void RemoveRef()
            {
                refs--;
                if (refs == 0 && currentEnumeration != null)
                {
                    var copy = currentEnumeration;
                    currentEnumeration = null;
                    copy.Source.Dispose();
                }
            }
        }

        public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
        {
            if (chunksize < 1) throw new InvalidOperationException();

            var wrapper =  new EnumeratorWrapper<T>(source);

            int currentPos = 0;
            T ignore;
            try
            {
                wrapper.AddRef();
                while (wrapper.Get(currentPos, out ignore))
                {
                    yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
                    currentPos += chunksize;
                }
            }
            finally
            {
                wrapper.RemoveRef();
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int i = 10;
            foreach (var group in Enumerable.Range(1, int.MaxValue).Skip(10000000).Chunk(3))
            {
                foreach (var n in group)
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
                if (i-- == 0) break;
            }


            var stuffs = Enumerable.Range(1, 10).Chunk(2).ToArray();

            foreach (var idx in new [] {3,2,1})
            {
                Console.Write("idx " + idx + " ");
                foreach (var n in stuffs[idx])
                {
                    Console.Write(n);
                    Console.Write(" ");
                }
                Console.WriteLine();
            }

            /*

10000001 10000002 10000003
10000004 10000005 10000006
10000007 10000008 10000009
10000010 10000011 10000012
10000013 10000014 10000015
10000016 10000017 10000018
10000019 10000020 10000021
10000022 10000023 10000024
10000025 10000026 10000027
10000028 10000029 10000030
10000031 10000032 10000033
idx 3 7 8
idx 2 5 6
idx 1 3 4
             */

            Console.ReadKey();


        }

    }
}

チャンクの順不同の繰り返しに対して導入できる最適化のラウンドもありますが、これはここでは範囲外です。

どの方法を選ぶべきですか?それはあなたが解決しようとしている問題に完全に依存します。あなたが最初の欠陥に関心がないならば、単純な答えは信じられないほど魅力的です。

ほとんどのメソッドと同じように、これはマルチスレッドでは安全ではありません。スレッドセーフにしたい場合はEnumeratorWrapperを修正する必要があります。 。

93
Sam Saffron

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が有効になるので、結果のシーケンスに対して他の操作を実行できます。


Sam's answer に照らして、私はこれなしでこれをするより簡単な方法があると感じました:

  • リストをもう一度繰り返します(私はもともとしませんでした)。
  • チャンクを解放する前にグループ内のアイテムを具体化する(アイテムの大きなチャンクの場合、メモリの問題があるでしょう)
  • Samが投稿したすべてのコード

そうは言っても、これは私が拡張メソッドで 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());
}

MoveNextChunkSequenceに渡されたIEnumerator<T>上で既に呼び出されているので、 Current によって返される項目を生成し、それからcountをインクリメントして、chunkSize項目を超えない反復のたびに、シーケンス内の次の項目に移動します(ただし、生成された項目の数がチャンクサイズを超えると、短絡されます)。

項目が残っていない場合、InternalChunkメソッドは外側のループでもう1回パスを実行しますが、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,

重要な注意点として、子シーケンス全体を流したり、親シーケンスのどの時点でも中断しなかったりすると、これはnotで動作します。これは重要な注意点ですが、あなたのユースケースがシーケンスのシーケンスのevery要素を消費するという場合、これはあなたのために働きます。

さらに、 Sam'sはある時点でやった と同じように、あなたがその順番でプレイした場合、それは奇妙なことをするでしょう。

64
casperOne

わかりました、これが私の考えです。

  • 完全に怠惰:無限の列挙型に作用
  • 中間コピー/バッファリングなし
  • O(n)実行時間
  • 内部シーケンスが部分的にしか消費されていない場合にも機能します。
public static IEnumerable<IEnumerable<T>> Chunks<T>(this IEnumerable<T> enumerable,
                                                    int chunkSize)
{
    if (chunkSize < 1) throw new ArgumentException("chunkSize must be positive");

    using (var e = enumerable.GetEnumerator())
    while (e.MoveNext())
    {
        var remaining = chunkSize;    // elements remaining in the current chunk
        var innerMoveNext = new Func<bool>(() => --remaining > 0 && e.MoveNext());

        yield return e.GetChunk(innerMoveNext);
        while (innerMoveNext()) {/* discard elements skipped by inner iterator */}
    }
}

private static IEnumerable<T> GetChunk<T>(this IEnumerator<T> e,
                                          Func<bool> innerMoveNext)
{
    do yield return e.Current;
    while (innerMoveNext());
}

使用例

var src = new [] {1, 2, 3, 4, 5, 6}; 

var c3 = src.Chunks(3);      // {{1, 2, 3}, {4, 5, 6}}; 
var c4 = src.Chunks(4);      // {{1, 2, 3, 4}, {5, 6}}; 

var sum   = c3.Select(c => c.Sum());    // {6, 15}
var count = c3.Count();                 // 2
var take2 = c3.Select(c => c.Take(2));  // {{1, 2}, {4, 5}}

説明

このコードは、2つのyieldベースの反復子をネストすることによって機能します。

外側のイテレータは、内側の(チャンク)イテレータによっていくつの要素が効果的に消費されたかを追跡しなければなりません。これはinnerMoveNext()remainingを閉じることによって行われます。チャンクの消費されていない要素は、次のチャンクが外部イテレータによって生成される前に破棄されます。内部の列挙型が(完全に)消費されていない場合(例:c3.Count()は6を返す)、そうでなければ矛盾する結果を得るのでこれは必要です。

注:答えは、@ aolszowkaによって指摘された欠点に対処するために更新されました。

45
3dGrabber

完全に怠惰で、数えたりコピーしたりしないでください。

public static class EnumerableExtensions
{

  public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int len)
  {
     if (len == 0)
        throw new ArgumentNullException();

     var enumer = source.GetEnumerator();
     while (enumer.MoveNext())
     {
        yield return Take(enumer.Current, enumer, len);
     }
  }

  private static IEnumerable<T> Take<T>(T head, IEnumerator<T> tail, int len)
  {
     while (true)
     {
        yield return head;
        if (--len == 0)
           break;
        if (tail.MoveNext())
           head = tail.Current;
        else
           break;
     }
  }
}
14
user1583558

私は次の提案が最速だろうと思います。私は、Array.Copyを使用する能力のためにソースEnumerableの遅延を犠牲にしていて、そして私のそれぞれのサブリストの長さを事前に知っています。

public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> items, int size)
{
    T[] array = items as T[] ?? items.ToArray();
    for (int i = 0; i < array.Length; i+=size)
    {
        T[] chunk = new T[Math.Min(size, array.Length - i)];
        Array.Copy(array, i, chunk, 0, chunk.Length);
        yield return chunk;
    }
}
12

@ JaredParのソリューションを改良して、真の遅延評価を行うことができます。同じキーを持つ連続した要素のグループを生成する GroupAdjacentBy メソッドを使用します。

sequence
.Select((x, i) => new { Value = x, Index = i })
.GroupAdjacentBy(x=>x.Index/3)
.Select(g=>g.Select(x=>x.Value))

グループは1つずつ生成されるため、この解決策は長いまたは無限のシーケンスで効率的に機能します。

9
Colonel Panic

私は数年前にClump拡張メソッドを書きました。うまく機能し、ここで最も速い実装です。 :P

/// <summary>
/// Clumps items into same size lots.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source list of items.</param>
/// <param name="size">The maximum size of the clumps to make.</param>
/// <returns>A list of list of items, where each list of items is no bigger than the size given.</returns>
public static IEnumerable<IEnumerable<T>> Clump<T>(this IEnumerable<T> source, int size)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (size < 1)
        throw new ArgumentOutOfRangeException("size", "size must be greater than 0");

    return ClumpIterator<T>(source, size);
}

private static IEnumerable<IEnumerable<T>> ClumpIterator<T>(IEnumerable<T> source, int size)
{
    Debug.Assert(source != null, "source is null.");

    T[] items = new T[size];
    int count = 0;
    foreach (var item in source)
    {
        items[count] = item;
        count++;

        if (count == size)
        {
            yield return items;
            items = new T[size];
            count = 0;
        }
    }
    if (count > 0)
    {
        if (count == size)
            yield return items;
        else
        {
            T[] tempItems = new T[count];
            Array.Copy(items, tempItems, count);
            yield return tempItems;
        }
    }
}
8

System.Interactive はこの目的のためにBuffer()を提供します。簡単なテストで、パフォーマンスがSamのソリューションに似ていることがわかります。

8
dahlbyk

これは私が数ヶ月前に書いたリスト分割ルーチンです。

public static List<List<T>> Chunk<T>(
    List<T> theList,
    int chunkSize
)
{
    List<List<T>> result = theList
        .Select((x, i) => new {
            data = x,
            indexgroup = i / chunkSize
        })
        .GroupBy(x => x.indexgroup, x => x.data)
        .Select(g => new List<T>(g))
        .ToList();

    return result;
}
6
Amy B

これは昔からの質問ですが、これは私が最終的に得たものです。列挙型を一度だけ列挙しますが、各パーティションのリストを作成します。いくつかの実装がするようにToArray()が呼ばれるとき、それは予想外の振舞いに苦しみません:

    public static IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int chunkSize)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        if (chunkSize < 1)
        {
            throw new ArgumentException("Invalid chunkSize: " + chunkSize);
        }

        using (IEnumerator<T> sourceEnumerator = source.GetEnumerator())
        {
            IList<T> currentChunk = new List<T>();
            while (sourceEnumerator.MoveNext())
            {
                currentChunk.Add(sourceEnumerator.Current);
                if (currentChunk.Count == chunkSize)
                {
                    yield return currentChunk;
                    currentChunk = new List<T>();
                }
            }

            if (currentChunk.Any())
            {
                yield return currentChunk;
            }
        }
    }
5
aolszowka

私はこの小さなスニペットが非常にうまく仕事をしているのを見つけます。

public static IEnumerable<List<T>> Chunked<T>(this List<T> source, int chunkSize)
{
    var offset = 0;

    while (offset < source.Count)
    {
        yield return source.GetRange(offset, Math.Min(source.Count - offset, chunkSize));
        offset += chunkSize;
    }
}
5
erlando

David Bのソリューションが最も効果的であることがわかりました。しかし、我々はそれをより一般的な解決策に適応させた。

list.GroupBy(item => item.SomeProperty) 
   .Select(group => new List<T>(group)) 
   .ToArray();
4
mwjackson

これはどうですか?

var input = new List<string> { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };
var k = 3

var res = Enumerable.Range(0, (input.Count - 1) / k + 1)
                    .Select(i => input.GetRange(i * k, Math.Min(k, input.Count - i * k)))
                    .ToList();

私の知る限りでは、 GetRange() はアイテムの数に関して線形です。だからこれはうまくいくはずです。

4
Roman Pekar

次の解決策は、私が思いつくことができる最もコンパクトなものです(On)。

public static IEnumerable<T[]> Chunk<T>(IEnumerable<T> source, int chunksize)
{
    var list = source as IList<T> ?? source.ToList();
    for (int start = 0; start < list.Count; start += chunksize)
    {
        T[] chunk = new T[Math.Min(chunksize, list.Count - start)];
        for (int i = 0; i < chunk.Length; i++)
            chunk[i] = list[start + i];

        yield return chunk;
    }
}

古いコードですが、これは私が使ってきたものです。

    public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        var toReturn = new List<T>(max);
        foreach (var item in source)
        {
            toReturn.Add(item);
            if (toReturn.Count == max)
            {
                yield return toReturn;
                toReturn = new List<T>(max);
            }
        }
        if (toReturn.Any())
        {
            yield return toReturn;
        }
    }
4
Robert McKee

リストがsystem.collections.generic型の場合は、使用可能な "CopyTo"メソッドを使用して、配列の要素を他のサブ配列にコピーできます。コピーする開始要素と要素数を指定します。

元のリストのクローンを3つ作成し、各リストの[RemoveRange]を使用してリストを希望のサイズに縮小することもできます。

あるいはそれを実行するためのヘルパーメソッドを作成するだけです。

3
Jobo

これは古い解決策ですが、私は別のアプローチを取っていました。目的のオフセットに移動するにはSkipを、目的の要素数を抽出するにはTakeを使用します。

public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, 
                                                   int chunkSize)
{
    if (chunkSize <= 0)
        throw new ArgumentOutOfRangeException($"{nameof(chunkSize)} should be > 0");

    var nbChunks = (int)Math.Ceiling((double)source.Count()/chunkSize);

    return Enumerable.Range(0, nbChunks)
                     .Select(chunkNb => source.Skip(chunkNb*chunkSize)
                     .Take(chunkSize));
}
2
Bertrand

もう一つの方法は RxBuffer operator を使うことです

//using System.Linq;
//using System.Reactive.Linq;
//using System.Reactive.Threading.Tasks;

var observableBatches = anAnumerable.ToObservable().Buffer(size);

var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();
1
frhack

パッケージ化された/維持されたソリューションに興味がある人のために、 MoreLINQ ライブラリはあなたの要求された振る舞いにマッチする Batch 拡張メソッドを提供します:

IEnumerable<char> source = "Example string";
IEnumerable<IEnumerable<char>> chunksOfThreeChars = source.Batch(3);

Batchの実装Cameron MacFarland's answer に似ていますが、チャンク/バッチを変換して返す前にオーバーロードが追加されており、非常にうまく機能します。

1
Kevinoid

私の2セントを入れるだけです。リストを「バケット化」(左から右に視覚化)したい場合は、次のようにします。

 public static List<List<T>> Buckets<T>(this List<T> source, int numberOfBuckets)
    {
        List<List<T>> result = new List<List<T>>();
        for (int i = 0; i < numberOfBuckets; i++)
        {
            result.Add(new List<T>());
        }

        int count = 0;
        while (count < source.Count())
        {
            var mod = count % numberOfBuckets;
            result[mod].Add(source[count]);
            count++;
        }
        return result;
    }
1
user1883961

モジュラー分割の使用

public IEnumerable<IEnumerable<string>> Split(IEnumerable<string> input, int chunkSize)
{
    var chunks = (int)Math.Ceiling((double)input.Count() / (double)chunkSize);
    return Enumerable.Range(0, chunks).Select(id => input.Where(s => s.GetHashCode() % chunks == id));
}
1
Janosz G.

それで Sam Saffron のアプローチと同じくらいパフォーマンス的です。

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be greater than zero.");

    return BatchImpl(source, size).TakeWhile(x => x.Any());
}

static IEnumerable<IEnumerable<T>> BatchImpl<T>(this IEnumerable<T> source, int size)
{
    var values = new List<T>();
    var group = 1;
    var disposed = false;
    var e = source.GetEnumerator();

    try
    {
        while (!disposed)
        {
            yield return GetBatch(e, values, group, size, () => { e.Dispose(); disposed = true; });
            group++;
        }
    }
    finally
    {
        if (!disposed)
            e.Dispose();
    }
}

static IEnumerable<T> GetBatch<T>(IEnumerator<T> e, List<T> values, int group, int size, Action dispose)
{
    var min = (group - 1) * size + 1;
    var max = group * size;
    var hasValue = false;

    while (values.Count < min && e.MoveNext())
    {
        values.Add(e.Current);
    }

    for (var i = min; i <= max; i++)
    {
        if (i <= values.Count)
        {
            hasValue = true;
        }
        else if (hasValue = e.MoveNext())
        {
            values.Add(e.Current);
        }
        else
        {
            dispose();
        }

        if (hasValue)
            yield return values[i - 1];
        else
            yield break;
    }
}

}

0
leandromoh

私は主な答えを取り、それをどこに分割するかを決定するためのIOCコンテナになるようにしました。 (答えを探している間にこの記事を読む際に、本当に3つのアイテムに分割しようと思っている人は?

このメソッドは、必要に応じて任意の種類のアイテムに分割することを可能にします。

public static List<List<T>> SplitOn<T>(List<T> main, Func<T, bool> splitOn)
{
    int groupIndex = 0;

    return main.Select( item => new 
                             { 
                               Group = (splitOn.Invoke(item) ? ++groupIndex : groupIndex), 
                               Value = item 
                             })
                .GroupBy( it2 => it2.Group)
                .Select(x => x.Select(v => v.Value).ToList())
                .ToList();
}

したがって、OPの場合、コードは次のようになります。

var it = new List<string>()
                       { "a", "g", "e", "w", "p", "s", "q", "f", "x", "y", "i", "m", "c" };

int index = 0; 
var result = SplitOn(it, (itm) => (index++ % 3) == 0 );
0
ΩmegaMan

無限の発電機で働くことができます:

a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1)))
 .Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1)))
 .Where((x, i) => i % 3 == 0)

デモコード: https://ideone.com/GKmL7M

using System;
using System.Collections.Generic;
using System.Linq;

public class Test
{
  private static void DoIt(IEnumerable<int> a)
  {
    Console.WriteLine(String.Join(" ", a));

    foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
      Console.WriteLine(String.Join(" ", x));

    Console.WriteLine();
  }

  public static void Main()
  {
    DoIt(new int[] {1});
    DoIt(new int[] {1, 2});
    DoIt(new int[] {1, 2, 3});
    DoIt(new int[] {1, 2, 3, 4});
    DoIt(new int[] {1, 2, 3, 4, 5});
    DoIt(new int[] {1, 2, 3, 4, 5, 6});
  }
}
1

1 2

1 2 3
1 2 3

1 2 3 4
1 2 3

1 2 3 4 5
1 2 3

1 2 3 4 5 6
1 2 3
4 5 6

しかし実際にはlinqなしで対応するメソッドを書くことを好むでしょう。

0
Qwertiy