Pertanyaan Bagi sebuah IEnumerable ke dalam potongan-potongan berukuran tetap (kembalikan sebuah IEnumerable > di mana urutan bagian dalam dari panjang tetap) [duplikat]


Pertanyaan ini sudah memiliki jawaban di sini:

Saya ingin mengambil IEnumerable<T> dan membaginya menjadi potongan berukuran tetap.

Saya memiliki ini, tetapi tampaknya janggal karena semua daftar pembuatan / penyalinan:

private static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
    List<T> partition = new List<T>(partitionSize);
    foreach (T item in items)
    {
        partition.Add(item);
        if (partition.Count == partitionSize)
        {
            yield return partition;
            partition = new List<T>(partitionSize);
        }
    }
    // Cope with items.Count % partitionSize != 0
    if (partition.Count > 0) yield return partition;
}

Adakah sesuatu yang lebih idiomatis?

EDIT: Meskipun ini telah ditandai sebagai duplikat dari Bagilah array ke dalam array array subsequence tidak - pertanyaan itu berkaitan dengan pemisahan array, padahal ini tentang IEnumerable<T>. Selain itu, pertanyaan itu menuntut bahwa pelanjutan terakhir adalah empuk. Kedua pertanyaan itu sangat berkaitan tetapi tidak sama.


34
2017-12-04 18:35


asal


Jawaban:


Anda dapat mencoba menerapkan metode Batch yang disebutkan di atas sendiri seperti ini:

    static class MyLinqExtensions 
    { 
        public static IEnumerable<IEnumerable<T>> Batch<T>( 
            this IEnumerable<T> source, int batchSize) 
        { 
            using (var enumerator = source.GetEnumerator()) 
                while (enumerator.MoveNext()) 
                    yield return YieldBatchElements(enumerator, batchSize - 1); 
        } 

        private static IEnumerable<T> YieldBatchElements<T>( 
            IEnumerator<T> source, int batchSize) 
        { 
            yield return source.Current; 
            for (int i = 0; i < batchSize && source.MoveNext(); i++) 
                yield return source.Current; 
        } 
    }

Saya telah mengambil kode ini dari http://blogs.msdn.com/b/pfxteam/archive/2012/11/16/plinq-and-int32-maxvalue.aspx.

MEMPERBARUI: Harap dicatat, bahwa penerapan ini tidak hanya mengevaluasi secara sembarang kumpulan tetapi juga barang-barang di dalam batch, yang berarti hanya akan menghasilkan hasil yang benar ketika batch dihitung hanya setelah semua batch sebelumnya dicacah. Sebagai contoh:

public static void Main(string[] args)
{
    var xs = Enumerable.Range(1, 20);
    Print(xs.Batch(5).Skip(1)); // should skip first batch with 5 elements
}

public static void Print<T>(IEnumerable<IEnumerable<T>> batches)
{
    foreach (var batch in batches)
    {
        Console.WriteLine($"[{string.Join(", ", batch)}]");
    }
}

akan menampilkan:

[2, 3, 4, 5, 6] //only first element is skipped.
[7, 8, 9, 10, 11]
[12, 13, 14, 15, 16]
[17, 18, 19, 20]

Jadi, jika Anda menggunakan case mengasumsikan batching ketika batch dievaluasi secara berurutan, maka solusi malas di atas akan berfungsi, sebaliknya jika Anda tidak dapat menjamin pemrosesan batch berurutan secara ketat (misalnya ketika Anda ingin memproses batch secara paralel), Anda mungkin akan membutuhkan solusi yang dengan bersemangat menyebutkan konten batch, mirip dengan yang disebutkan dalam pertanyaan di atas atau di Lebih dari LINQ


55
2017-12-04 19:00



Rasanya seperti yang Anda inginkan dua blok iterator ("yield return metode "). Saya menulis metode ekstensi ini:

static class Extensions
{
  public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
  {
    return new PartitionHelper<T>(items, partitionSize);
  }

  private sealed class PartitionHelper<T> : IEnumerable<IEnumerable<T>>
  {
    readonly IEnumerable<T> items;
    readonly int partitionSize;
    bool hasMoreItems;

    internal PartitionHelper(IEnumerable<T> i, int ps)
    {
      items = i;
      partitionSize = ps;
    }

    public IEnumerator<IEnumerable<T>> GetEnumerator()
    {
      using (var enumerator = items.GetEnumerator())
      {
        hasMoreItems = enumerator.MoveNext();
        while (hasMoreItems)
          yield return GetNextBatch(enumerator).ToList();
      }
    }

    IEnumerable<T> GetNextBatch(IEnumerator<T> enumerator)
    {
      for (int i = 0; i < partitionSize; ++i)
      {
        yield return enumerator.Current;
        hasMoreItems = enumerator.MoveNext();
        if (!hasMoreItems)
          yield break;
      }
    }

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

10
2017-12-04 20:41



Mungkin?

public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
    return items.Select((item, inx) => new { item, inx })
                .GroupBy(x => x.inx / partitionSize)
                .Select(g => g.Select(x => x.item));
}

Ada yang sudah diimplementasikan juga: morelinq Batch.


8
2017-12-04 18:45



Solusi paling gila (dengan Ekstensi Reaktif):

public static IEnumerable<IList<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
    return items
            .ToObservable() // Converting sequence to observable sequence
            .Buffer(partitionSize) // Splitting it on spececified "partitions"
            .ToEnumerable(); // Converting it back to ordinary sequence
}

Saya tahu bahwa saya mengubah tanda tangan tetapi bagaimanapun kita semua tahu bahwa kita akan memiliki beberapa koleksi ukuran tetap sebagai sepotong.

BTW jika Anda akan menggunakan blok iterator jangan lupa untuk membagi implementasi Anda menjadi dua metode untuk memvalidasi argumen dengan bersemangat!


7
2017-12-04 19:24



Untuk solusi elegan, Anda juga dapat melihat LebihLinq.Batch.

Ini batch urutan sumber ke dalam ukuran ember.

Contoh:

int[] ints = new int[] {1,2,3,4,5,6};
var batches = ints.Batch(2); // batches -> [0] : 1,2 ; [1]:3,4 ; [2] :5,6

3
2017-12-04 18:51



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

2
2017-12-04 18:43



Anda dapat melakukan ini menggunakan kelebihan muatan Enumerable.GroupBy dan mengambil keuntungan dari pembagian integer.

return items.Select((element, index) => new { Element = element, Index = index })
    .GroupBy(obj => obj.Index / partitionSize, (_, partition) => partition);

1
2017-12-04 18:46



Bagaimana dengan kelas-kelas partitioner di dalam System.Collections.Concurrent ruang nama?


0
2017-12-04 18:42