Diberikan koleksi, apakah ada cara untuk mendapatkan elemen N terakhir dari koleksi itu? Jika tidak ada metode dalam kerangka kerja, apa cara terbaik untuk menulis metode ekstensi untuk melakukan ini?
collection.Skip(Math.Max(0, collection.Count() - N));
Pendekatan ini mempertahankan pesanan barang tanpa ketergantungan pada penyortiran apa pun, dan memiliki kompatibilitas luas di beberapa penyedia LINQ.
Penting untuk berhati-hati agar tidak menelepon Skip
dengan nomor negatif. Beberapa penyedia, seperti Kerangka Entitas, akan menghasilkan ArgumentException ketika disajikan dengan argumen negatif. Panggilan untuk Math.Max
menghindari ini dengan rapi.
Kelas di bawah ini memiliki semua hal penting untuk metode ekstensi, yaitu: kelas statis, metode statis, dan penggunaan this
kata kunci.
public static class MiscExtensions
{
// Ex: collection.TakeLast(5);
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N)
{
return source.Skip(Math.Max(0, source.Count() - N));
}
}
Catatan singkat tentang kinerja:
Karena panggilan ke Count()
dapat menyebabkan enumerasi struktur data tertentu, pendekatan ini memiliki risiko menyebabkan dua lewati data. Ini sebenarnya bukan masalah bagi sebagian besar enumerable; pada kenyataannya, optimisasi sudah ada untuk Daftar, Array, dan bahkan kueri EF untuk mengevaluasi Count()
operasi dalam waktu O (1).
Namun, jika Anda harus menggunakan enumerable hanya maju dan ingin menghindari membuat dua lintasan, pertimbangkan algoritma satu lintasan seperti dijelaskan Lasse V. Karlsen atau Mark Byers . Kedua pendekatan ini menggunakan buffer sementara untuk menahan item saat enumerasi, yang dihasilkan setelah akhir koleksi ditemukan.
List
s, danLinkedList
s, solusi James cenderung lebih cepat, meskipun bukan dengan urutan besarnya. Jika IEnumerable dihitung (melalui Enumerable.Range, misal), solusi James membutuhkan waktu lebih lama. Saya tidak bisa memikirkan cara apa pun untuk menjamin satu pass tanpa mengetahui sesuatu tentang implementasi atau menyalin nilai ke struktur data yang berbeda.UPDATE: Untuk mengatasi masalah clintp: a) Menggunakan metode TakeLast () yang saya definisikan di atas menyelesaikan masalah, tetapi jika Anda benar-benar ingin melakukannya tanpa metode tambahan, maka Anda hanya harus mengenalinya sementara Enumerable.Reverse () dapat berupa digunakan sebagai metode ekstensi, Anda tidak perlu menggunakannya seperti itu:
sumber
List<string> mystring = new List<string>() { "one", "two", "three" }; mystring = mystring.Reverse().Take(2).Reverse();
Saya mendapatkan kesalahan kompiler karena .Reverse () mengembalikan batal dan kompiler memilih metode itu daripada Linq yang mengembalikan IEnumerable. Saran?N
catatan terakhir, Anda dapat melewati yang keduaReverse
.Catatan : Saya melewatkan judul pertanyaan Anda yang mengatakan Menggunakan Linq , jadi jawaban saya sebenarnya tidak menggunakan Linq.
Jika Anda ingin menghindari caching salinan non-malas dari seluruh koleksi, Anda bisa menulis metode sederhana yang melakukannya dengan menggunakan daftar tertaut.
Metode berikut akan menambahkan setiap nilai yang ditemukan dalam koleksi asli ke dalam daftar tertaut, dan memangkas daftar tertaut ke jumlah item yang diperlukan. Karena itu menjaga daftar yang ditautkan terpotong ke jumlah item ini sepanjang waktu melalui iterasi melalui koleksi, itu hanya akan menyimpan salinan paling banyak N item dari koleksi asli.
Anda tidak perlu tahu jumlah item dalam koleksi asli, atau mengulanginya lebih dari satu kali.
Pemakaian:
Metode ekstensi:
sumber
Berikut adalah metode yang berfungsi pada enumerable apa pun tetapi hanya menggunakan penyimpanan sementara O (N):
Pemakaian:
Ia bekerja dengan menggunakan penyangga cincin ukuran N untuk menyimpan elemen-elemen seperti yang dilihatnya, menimpa elemen lama dengan yang baru. Ketika akhir enumerable tercapai, buffer cincin berisi elemen N terakhir.
sumber
n
..NET Core 2.0+ menyediakan metode LINQ
TakeLast()
:https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takelast
contoh :
sumber
netcoreapp1.x
) tetapi hanya untuk v2.0 & v2.1 dari dotnetcore (netcoreapp2.x
). Mungkin saja Anda menargetkan kerangka kerja lengkap (mis.net472
) Yang juga tidak didukung. (.net lib standar dapat digunakan oleh salah satu di atas tetapi hanya dapat mengekspos API tertentu khusus untuk kerangka kerja target. lihat docs.microsoft.com/en-us/dotnet/standard/frameworks )Saya terkejut bahwa tidak ada yang menyebutkannya, tetapi SkipWhile memang memiliki metode yang menggunakan indeks elemen .
Satu-satunya manfaat yang dapat dirasakan yang disajikan solusi ini daripada yang lain adalah bahwa Anda dapat memiliki opsi untuk menambahkan predikat untuk membuat kueri LINQ yang lebih kuat dan efisien, alih-alih memiliki dua operasi terpisah yang melintasi IEnumerable dua kali.
sumber
Gunakan EnumerableEx.TakeLast di Sistem RX.Rakitan interaktif. Ini adalah implementasi O (N) seperti @ Mark's, tetapi menggunakan antrian alih-alih konstruksi cincin-buffer (dan mengeluarkan item ketika mencapai kapasitas buffer).
(NB: Ini adalah versi IEnumerable - bukan versi IObservable, meskipun implementasi keduanya cukup identik)
sumber
Queue<T>
diimplementasikan menggunakan buffer lingkaran ?Jika Anda berurusan dengan koleksi dengan kunci (misalnya entri dari database) solusi cepat (yaitu lebih cepat dari jawaban yang dipilih) akan menjadi
sumber
Jika Anda tidak keberatan menggunakan Rx sebagai bagian dari monad, Anda dapat menggunakan
TakeLast
:sumber
Jika menggunakan pustaka pihak ketiga adalah sebuah opsi, MoreLinq menentukan
TakeLast()
mana yang melakukan hal ini.sumber
Saya mencoba menggabungkan efisiensi dan kesederhanaan dan akhirnya dengan ini:
Tentang kinerja: Di C #,
Queue<T>
diimplementasikan menggunakan buffer lingkaran sehingga tidak ada instantiasi objek yang dilakukan setiap loop (hanya ketika antrian tumbuh dewasa). Saya tidak menetapkan kapasitas antrian (menggunakan konstruktor khusus) karena seseorang mungkin memanggil ekstensi ini dengancount = int.MaxValue
. Untuk kinerja tambahan, Anda dapat memeriksa apakah sumber mengimplementasikanIList<T>
dan jika ya, ekstrak langsung nilai terakhir menggunakan indeks array.sumber
Agak tidak efisien untuk mengambil N terakhir dari sebuah koleksi menggunakan LINQ karena semua solusi di atas memerlukan iterasi di seluruh koleksi.
TakeLast(int n)
diSystem.Interactive
juga memiliki masalah ini.Jika Anda memiliki daftar, hal yang lebih efisien untuk dilakukan adalah mengirisnya menggunakan metode berikut
dengan
dan beberapa kasus uji
sumber
Saya tahu sudah terlambat untuk menjawab pertanyaan ini. Tetapi jika Anda bekerja dengan koleksi tipe IList <> dan Anda tidak peduli dengan urutan koleksi yang dikembalikan, maka metode ini bekerja lebih cepat. Saya telah menggunakan jawaban Mark Byers dan membuat sedikit perubahan. Jadi sekarang metode TakeLast adalah:
Untuk tes saya telah menggunakan metode Mark Byers dan kbrimington's andswer . Ini adalah tes:
Dan berikut ini adalah hasil untuk mengambil 10 elemen:
dan untuk mengambil 1000001 hasil elemen adalah:
sumber
Inilah solusi saya:
Kode ini sedikit chunky, tetapi sebagai komponen drop-in yang dapat digunakan kembali, harus berfungsi sebaik mungkin di sebagian besar skenario, dan itu akan membuat kode yang menggunakannya bagus dan ringkas. :-)
Saya
TakeLast
untuk non-IList`1
didasarkan pada algoritma buffer ring yang sama dengan yang dijawab oleh @Mark Byers dan @MackieChan. Sangat menarik betapa miripnya mereka - saya menulis milik saya sepenuhnya secara independen. Kira hanya ada satu cara untuk melakukan buffer cincin dengan benar. :-)Melihat jawaban @ kbrimington, pemeriksaan tambahan dapat ditambahkan ke sini untuk
IQuerable<T>
kembali ke pendekatan yang bekerja dengan baik dengan Kerangka Entitas - dengan asumsi bahwa apa yang saya miliki saat ini tidak.sumber
Di bawah contoh nyata bagaimana mengambil 3 elemen terakhir dari koleksi (array):
sumber
Menggunakan Metode Ini Untuk Mendapatkan Semua Jangkauan Tanpa Kesalahan
sumber
Implementasinya sedikit berbeda dengan penggunaan buffer lingkaran. Benchmark menunjukkan bahwa metode ini sekitar dua kali lebih cepat daripada yang menggunakan Antrian (implementasi TakeLast di System.Linq ), namun bukan tanpa biaya - perlu buffer yang tumbuh bersama dengan jumlah elemen yang diminta, bahkan jika Anda memiliki koleksi kecil Anda bisa mendapatkan alokasi memori yang sangat besar.
sumber