45

(LINQを使用して)リストを取得し、8番目のエントリごとに元のリストを分割するリストのリストに分割するにはどうすればよいですか?

このようなものにはスキップやテイクが含まれると思いますが、私はまだLINQにかなり慣れていません。

編集:C#/.Net3.5の使用

Edit2:この質問の言い回しは、他の「重複した」質問とは異なります。問題は似ていますが、この質問の答えは優れています。「受け入れられた」答えは非常に堅実であり(yieldステートメントを含む)、Jon SkeetがMoreLinqを使用することを提案しています(「その他」の質問では推奨されません)。重複は、問題の再調査を強制するという点で優れている場合があります。

4

7 に答える 7

56

次の拡張方法を使用して、入力をサブセットに分割します

public static class IEnumerableExtensions
{
    public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        List<T> 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;
        }
    }
}
于 2010-09-22T20:40:35.337 に答える
44

MoreLINQには、Batchメソッドのようなメソッドがあります。

// As IEnumerable<IEnumerable<T>>
var items = list.Batch(8);

また

// As IEnumerable<List<T>>
var items = list.Batch(8, seq => seq.ToList());
于 2010-09-22T20:39:48.140 に答える
15

MoreLinqのようなライブラリを使用する方が良いですが、本当に「プレーンLINQ」を使用してこれを行う必要がある場合は、次を使用できますGroupBy

var sequence = new[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

var result = sequence.Select((x, i) => new {Group = i/8, Value = x})
                     .GroupBy(item => item.Group, g => g.Value)
                     .Select(g => g.Where(x => true));

// result is: { {1,2,3,4,5,6,7,8}, {9,10,11,12,13,14,15,16} }

基本的に、Select()消費されている値のインデックスを提供するバージョンを使用し、インデックスを8で割って、各値が属するグループを識別します。次に、このグループ化キーによってシーケンスをグループ化します。最後は、ダウンをSelectaに減らすだけです(であるため、厳密には必要ありません)。IGrouping<>IEnumerable<IEnumerable<T>>IGroupingIEnumerable

8例の定数を因数分解し、指定されたパラメーターに置き換えることで、これを再利用可能なメソッドに変えるのは簡単です。これは必ずしも最も洗練されたソリューションではなく、もはや怠惰なストリーミングソリューションではありません...しかし、それは機能します。

イテレータブロック(yield return)を使用して独自の拡張メソッドを作成することもできます。これにより、パフォーマンスが向上し、使用するメモリが。より少なくなりますGroupBy。これは、MoreLinqのBatch()メソッドがIIRCを実行する方法です。

于 2010-09-22T20:47:44.187 に答える
3

元のLinqデザイナーが念頭に置いていたものではありませんが、GroupByのこの誤用を確認してください。

public static IEnumerable<IEnumerable<T>> BatchBy<T>(this IEnumerable<T> items, int batchSize)
{
    var count = 0;
    return items.GroupBy(x => (count++ / batchSize)).ToList();
}

[TestMethod]
public void BatchBy_breaks_a_list_into_chunks()
{
    var values = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    var batches = values.BatchBy(3);
    batches.Count().ShouldEqual(4);
    batches.First().Count().ShouldEqual(3);
    batches.Last().Count().ShouldEqual(1);
}

この質問で「ゴルフ」賞を受賞したと思います。ToList出力で何かを実行する前に、グループ化が実際に実行されていることを確認する必要があるため、これは非常に重要です。を削除するToListと、いくつかの奇妙な副作用が発生します。

于 2010-10-13T21:20:06.927 に答える
0

Takeは、取得したエントリを削除しないため、あまり効率的ではありません。

単純なループを使用しないのはなぜですか。

public IEnumerable<IList<T>> Partition<T>(this/* <-- see extension methods*/ IEnumerable<T> src,int num)  
{  
    IEnumerator<T> enu=src.getEnumerator();  
    while(true)  
    {  
        List<T> result=new List<T>(num);  
        for(int i=0;i<num;i++)  
        {  
            if(!enu.MoveNext())  
            {  
                if(i>0)yield return result;  
                yield break;  
            }  
            result.Add(enu.Current);  
        }  
        yield return result;  
    }  
}
于 2010-09-22T20:52:39.183 に答える
0
from b in Enumerable.Range(0,8) select items.Where((x,i) => (i % 8) == b);
于 2010-10-14T02:42:15.430 に答える
0

最も簡単な解決策はMelによって与えられます:

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    int i = 0;
    return items.GroupBy(x => i++ / partitionSize).ToArray();
}

簡潔ですが遅いです。上記のメソッドは、IEnumerableを目的の固定サイズのチャンクに分割します。チャンクの総数は重要ではありません。IEnumerableを等しいサイズまたは等しいサイズに近いN個のチャンクに分割するには、次のようにします。

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, 
                                                   int numOfParts)
{
    int i = 0;
    return items.GroupBy(x => i++ % numOfParts);
}

物事をスピードアップするために、簡単なアプローチは次のようになります。

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, 
                                                       int partitionSize)
{
    if (partitionSize <= 0)
        throw new ArgumentOutOfRangeException("partitionSize");

    int innerListCounter = 0;
    int numberOfPackets = 0;
    foreach (var item in items)
    {
        innerListCounter++;
        if (innerListCounter == partitionSize)
        {
            yield return items.Skip(numberOfPackets * partitionSize).Take(partitionSize);
            innerListCounter = 0;
            numberOfPackets++;
        }
    }

    if (innerListCounter > 0)
        yield return items.Skip(numberOfPackets * partitionSize);
}

これは現在地球上にあるものよりも高速です:)ここSplitでの操作の同等の方法

于 2012-12-06T13:53:55.253 に答える