Dapatkah seseorang menyarankan cara untuk membuat kumpulan dengan ukuran tertentu di LINQ?
Idealnya, saya ingin dapat melakukan operasi dalam beberapa bagian yang dapat dikonfigurasi.
Anda tidak perlu menulis kode apa pun. Gunakan metode MoreLINQ Batch, yang mengelompokkan urutan sumber ke dalam bucket berukuran (MoreLINQ tersedia sebagai paket NuGet yang dapat Anda instal):
int size = 10;
var batches = sequence.Batch(size);
Yang diimplementasikan sebagai:
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
this IEnumerable<TSource> source, int size)
{
TSource[] bucket = null;
var count = 0;
foreach (var item in source)
{
if (bucket == null)
bucket = new TSource[size];
bucket[count++] = item;
if (count != size)
continue;
yield return bucket;
bucket = null;
count = 0;
}
if (bucket != null && count > 0)
yield return bucket.Take(count).ToArray();
}
Batch(new int[] { 1, 2 }, 1000000)
dan penggunaannya adalah:
KELUARAN:
sumber
GroupBy
memulai pencacahan, bukankah harus sepenuhnya menyebutkan sumbernya? Ini kehilangan evaluasi malas dari sumber dan dengan demikian, dalam beberapa kasus, semua manfaat dari batching!Jika Anda memulai dengan
sequence
didefinisikan sebagaiIEnumerable<T>
, dan Anda tahu bahwa itu dapat dengan aman disebutkan beberapa kali (misalnya karena ini adalah larik atau daftar), Anda dapat menggunakan pola sederhana ini untuk memproses elemen dalam kelompok:sumber
Semua hal di atas bekerja sangat buruk dengan batch besar atau ruang memori rendah. Harus menulis sendiri yang akan pipeline (perhatikan tidak ada akumulasi item di mana pun):
Sunting: Masalah yang diketahui dengan pendekatan ini adalah bahwa setiap batch harus dihitung dan dicacah sepenuhnya sebelum pindah ke batch berikutnya. Misalnya ini tidak berhasil:
sumber
Ini adalah implementasi Batch yang sepenuhnya malas, overhead rendah, dan satu fungsi yang tidak melakukan akumulasi apa pun. Berdasarkan (dan memperbaiki masalah dalam) solusi Nick Whaley dengan bantuan dari EricRoller.
Iterasi berasal langsung dari IEnumerable yang mendasarinya, sehingga elemen harus dihitung dalam urutan yang ketat, dan diakses tidak lebih dari sekali. Jika beberapa elemen tidak dikonsumsi dalam loop dalam, mereka akan dibuang (dan mencoba mengaksesnya lagi melalui iterator yang disimpan akan dilempar
InvalidOperationException: Enumeration already finished.
).Anda dapat menguji sampel lengkap di .NET Fiddle .
sumber
done
hanya dengan menelepone.Count()
setelahnyayield return e
. Anda perlu mengatur ulang loop di BatchInner untuk tidak memanggil perilaku yang tidak ditentukansource.Current
ifi >= size
. Ini akan menghilangkan kebutuhan untuk mengalokasikan yang baruBatchInner
untuk setiap batch.i
jadi ini tidak selalu lebih efisien daripada mendefinisikan kelas terpisah, tetapi menurut saya sedikit lebih bersih.Saya bertanya-tanya mengapa tidak ada yang pernah memposting solusi loop-for-sekolah lama. Ini salah satunya:
Kesederhanaan ini dimungkinkan karena metode Take:
Penolakan:
Menggunakan Skip dan Take inside the loop berarti enumerable akan dihitung beberapa kali. Ini berbahaya jika pencacahan ditunda. Ini dapat mengakibatkan beberapa eksekusi kueri database, atau permintaan web, atau file dibaca. Contoh ini secara eksplisit untuk penggunaan List yang tidak ditangguhkan, jadi ini bukan masalah. Ini masih merupakan solusi yang lambat karena lewati akan menghitung koleksi setiap kali dipanggil.
Ini juga dapat diselesaikan dengan menggunakan
GetRange
metode ini, tetapi memerlukan perhitungan ekstra untuk mengekstrak kemungkinan tumpukan sisa:Berikut adalah cara ketiga untuk menangani ini, yang bekerja dengan 2 loop. Ini memastikan bahwa koleksi dihitung hanya 1 kali !:
sumber
Skip
danTake
di dalam loop berarti enumerable akan disebutkan beberapa kali. Ini berbahaya jika pencacahan ditunda. Ini dapat mengakibatkan beberapa eksekusi kueri database, atau permintaan web, atau file dibaca. Dalam contoh Anda, Anda memilikiList
yang tidak ditangguhkan, jadi itu bukan masalah.Pendekatan yang sama seperti MoreLINQ, tetapi menggunakan List, bukan Array. Saya belum melakukan pembandingan, tetapi keterbacaan lebih penting bagi sebagian orang:
sumber
size
parameter ke Andanew List
untuk mengoptimalkan ukurannya.batch.Clear();
denganbatch = new List<T>();
Berikut ini adalah upaya peningkatan implementasi malas Nick Whaley ( tautan ) dan infogulch ( tautan )
Batch
. Yang ini ketat. Anda bisa menghitung batch dalam urutan yang benar, atau Anda mendapatkan pengecualian.Dan berikut adalah
Batch
implementasi malas untuk sumber tipeIList<T>
. Yang satu ini tidak membatasi pencacahan. Batch dapat dihitung sebagian, dalam urutan apapun, dan lebih dari satu kali. Namun, larangan untuk tidak mengubah koleksi selama pencacahan masih berlaku. Ini dicapai dengan membuat panggilan tiruan keenumerator.MoveNext()
sebelum menghasilkan potongan atau elemen apa pun. Kelemahannya adalah bahwa pencacah dibiarkan tidak tergesa-gesa, karena tidak diketahui kapan pencacahan akan selesai.sumber
Saya bergabung ini sangat terlambat tetapi saya menemukan sesuatu yang lebih menarik.
Jadi kita bisa gunakan di sini
Skip
danTake
untuk performa yang lebih baik.Selanjutnya saya memeriksa dengan 100000 catatan. Perulangan hanya membutuhkan lebih banyak waktu jika terjadi
Batch
Kode aplikasi konsol.
Waktu yang dibutuhkan Seperti ini.
Pertama - 00: 00: 00.0708, 00: 00: 00.0660
Kedua (Ambil dan Lewati Satu) - 00: 00: 00.0008, 00: 00: 00.0008
sumber
GroupBy
sepenuhnya menghitung sebelum menghasilkan satu baris. Ini bukan cara yang baik untuk melakukan batching.foreach (var batch in Ids2.Batch(5000))
kevar gourpBatch = Ids2.Batch(5000)
dan memeriksa hasil waktunya. atau tambahkan tolist kevar SecBatch = Ids2.Batch2(StartIndex, BatchSize);
saya akan tertarik jika hasil Anda untuk perubahan waktu.Jadi dengan topi fungsional, ini tampak sepele .... tetapi di C #, ada beberapa kerugian yang signifikan.
Anda mungkin akan melihat ini sebagai terbukanya IEnumerable (google itu dan Anda mungkin akan berakhir di beberapa dokumen Haskell, tapi mungkin ada beberapa F # hal yang menggunakan terungkap, jika Anda tahu F #, julingkan di dokumen Haskell dan itu akan membuat merasakan).
Unfold terkait dengan lipat ("agregat") kecuali daripada iterasi melalui input IEnumerable, iterasi melalui struktur data keluaran (hubungan yang mirip antara IEnumerable dan IObservable, sebenarnya saya pikir IObservable tidak menerapkan "terungkap" yang disebut menghasilkan. ..)
Lagi pula pertama-tama Anda memerlukan metode terungkap, saya pikir ini berfungsi (sayangnya pada akhirnya akan meledakkan tumpukan untuk "daftar" besar ... Anda dapat menulis ini dengan aman di F # menggunakan yield! daripada concat);
ini agak tumpul karena C # tidak menerapkan beberapa hal yang dianggap bahasa fungsional begitu saja ... tetapi pada dasarnya mengambil sebuah seed dan kemudian menghasilkan jawaban "Mungkin" dari elemen berikutnya di IEnumerable dan seed berikutnya (Maybe tidak ada di C #, jadi kami telah menggunakan IEnumerable untuk memalsukannya), dan menggabungkan sisa jawaban (saya tidak dapat menjamin kompleksitas "O (n?)" dari ini).
Setelah Anda selesai melakukannya;
semuanya terlihat cukup bersih ... Anda mengambil elemen "n" sebagai elemen "berikutnya" di IEnumerable, dan "tail" adalah sisa dari daftar yang belum diproses.
jika tidak ada apa-apa di kepala ... Anda selesai ... Anda mengembalikan "Tidak ada" (tetapi dipalsukan sebagai IEnumerable> kosong) ... jika tidak, Anda mengembalikan elemen kepala dan ekor untuk diproses.
Anda mungkin dapat melakukan ini menggunakan IObservable, mungkin ada metode seperti "Batch" yang sudah ada, dan Anda mungkin dapat menggunakannya.
Jika risiko stack overflows mengkhawatirkan (mungkin seharusnya), maka Anda harus menerapkan di F # (dan mungkin sudah ada beberapa library F # (FSharpX?) Dengan ini).
(Saya hanya melakukan beberapa tes dasar untuk ini, jadi mungkin ada bug aneh di sana).
sumber
Saya menulis implementasi IEnumerable kustom yang bekerja tanpa LINQ dan menjamin pencacahan tunggal atas data. Itu juga menyelesaikan semua ini tanpa memerlukan daftar dukungan atau array yang menyebabkan ledakan memori atas kumpulan data yang besar.
Berikut beberapa tes dasar:
Metode Ekstensi untuk mempartisi data.
Ini adalah kelas pelaksana
sumber
Saya tahu semua orang menggunakan sistem kompleks untuk melakukan pekerjaan ini, dan saya benar-benar tidak mengerti mengapa. Ambil dan lewati akan memungkinkan semua operasi tersebut menggunakan pemilihan umum dengan
Func<TSource,Int32,TResult>
fungsi transformasi. Suka:sumber
source
akan sangat sering diulang.Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next()))
.Hanya implementasi satu baris. Ia bekerja bahkan dengan daftar kosong, dalam hal ini Anda mendapatkan koleksi batch ukuran nol.
sumber
Cara lain adalah dengan menggunakan operator Rx Buffer
sumber
GetAwaiter().GetResult()
. Ini adalah bau kode untuk kode sinkron yang secara paksa memanggil kode asinkron.sumber