Saya memiliki objek Orang dengan properti DateOfBirth Nullable. Apakah ada cara untuk menggunakan LINQ untuk meminta daftar objek Orang untuk yang dengan nilai DateOfBirth paling awal / terkecil.
Inilah yang saya mulai dengan:
var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));
Nilai DateOfBirth kosong ditetapkan ke DateTime.MaxValue untuk mengesampingkan mereka dari pertimbangan Min (dengan asumsi setidaknya satu memiliki DOB yang ditentukan).
Tapi yang saya lakukan hanyalah mengatur firstBornDate menjadi nilai DateTime. Yang ingin saya dapatkan adalah objek Orang yang cocok dengan itu. Apakah saya perlu menulis permintaan kedua seperti:
var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);
Atau ada cara yang lebih ramping untuk melakukannya?
a.Min(x => x.foo);
max("find a word of maximal length in this sentence".split(), key=len)
mengembalikan string 'kalimat'. Dalam C #"find a word of maximal length in this sentence".Split().Max(word => word.Length)
menghitung bahwa 8 adalah panjang terpanjang kata apapun, tetapi tidak memberi tahu Anda apa kata terpanjang adalah .Jawaban:
sumber
curMin == null
?curMin
hanya bisanull
jika Anda menggunakanAggregate()
dengan benih itunull
.Sayangnya tidak ada metode bawaan untuk melakukan ini, tetapi cukup mudah untuk diterapkan untuk Anda sendiri. Inilah keberaniannya:
Contoh penggunaan:
Perhatikan bahwa ini akan mengeluarkan pengecualian jika urutannya kosong, dan akan mengembalikan elemen pertama dengan nilai minimal jika ada lebih dari satu.
Atau, Anda dapat menggunakan implementasi yang kami miliki di MoreLINQ , di MinBy.cs . (Ada yang sesuai
MaxBy
, tentu saja.)Instal melalui konsol manajer paket:
sumber
CATATAN: Saya menyertakan jawaban ini untuk kelengkapan karena OP tidak menyebutkan apa sumber datanya dan kami tidak boleh membuat asumsi apa pun.
Kueri ini memberikan jawaban yang benar, tetapi bisa lebih lambat karena mungkin harus mengurutkan semua item
People
, tergantung pada struktur data apaPeople
:UPDATE: Sebenarnya saya tidak seharusnya menyebut solusi ini "naif", tetapi pengguna tidak perlu tahu apa yang ia tanyakan. "Kelambatan" solusi ini tergantung pada data yang mendasarinya. Jika ini adalah array atau
List<T>
, maka LINQ to Objects tidak punya pilihan selain mengurutkan seluruh koleksi terlebih dahulu sebelum memilih item pertama. Dalam hal ini akan lebih lambat daripada solusi lain yang disarankan. Namun, jika ini adalah tabel LINQ ke SQL danDateOfBirth
merupakan kolom yang diindeks, maka SQL Server akan menggunakan indeks alih-alih menyortir semua baris.IEnumerable<T>
Implementasi kustom lain juga dapat menggunakan indeks (lihat i4o: Indexed LINQ , atau database objek db4o ) dan menjadikan solusi ini lebih cepat daripadaAggregate()
atauMaxBy()
/MinBy()
yang perlu mengulang seluruh koleksi sekali. Sebenarnya, LINQ to Objects bisa (secara teori) membuat case khususOrderBy()
untuk koleksi yang diurutkan sepertiSortedList<T>
, tapi tidak, sejauh yang saya tahu.sumber
Akan melakukan triknya
sumber
Jadi, Anda meminta
ArgMin
atauArgMax
. C # tidak memiliki API bawaan untuk itu.Saya telah mencari cara yang bersih dan efisien (O (n) pada waktunya) untuk melakukan ini. Dan saya rasa saya menemukan satu:
Bentuk umum dari pola ini adalah:
Khususnya, menggunakan contoh dalam pertanyaan asli:
Untuk C # 7.0 ke atas yang mendukung nilai tuple :
Untuk versi C # sebelum 7.0, tipe anonim dapat digunakan sebagai gantinya:
Mereka bekerja karena kedua nilai tuple dan jenis anonim memiliki comparers standar yang masuk akal: untuk (x1, y1) dan (x2, y2), pertama kali membandingkan
x1
vsx2
, kemudiany1
vsy2
. Itu sebabnya built-in.Min
dapat digunakan pada tipe-tipe itu.Dan karena tipe anonim dan nilai tuple adalah tipe nilai, keduanya harus sangat efisien.
CATATAN
Dalam
ArgMin
implementasi saya di atas, saya berasumsiDateOfBirth
untuk mengetikDateTime
untuk kesederhanaan dan kejelasan. Pertanyaan asli meminta untuk mengecualikan entri tersebut denganDateOfBirth
bidang nol :Itu bisa dicapai dengan pre-filtering
Jadi tidak penting untuk pertanyaan implementasi
ArgMin
atauArgMax
.CATATAN 2
Pendekatan di atas memiliki peringatan bahwa ketika ada dua instance yang memiliki nilai min yang sama, maka
Min()
implementasi akan mencoba untuk membandingkan instance sebagai tie-breaker. Namun, jika kelas instance tidak mengimplementasikanIComparable
, maka kesalahan runtime akan dilemparkan:Untungnya, ini masih bisa diperbaiki dengan agak bersih. Idenya adalah untuk mengasosiasikan "ID" jarak dengan setiap entri yang berfungsi sebagai tie-breaker yang jelas. Kita dapat menggunakan ID tambahan untuk setiap entri. Masih menggunakan usia orang sebagai contoh:
sumber
Solusi tanpa paket tambahan:
Anda juga dapat membungkusnya menjadi ekstensi:
dan dalam hal ini:
Ngomong-ngomong ... O (n ^ 2) bukan solusi terbaik. Paul Betts memberikan solusi yang lebih gemuk daripada saya. Tapi solusi saya masih LINQ dan itu lebih sederhana dan lebih pendek daripada solusi lain di sini.
sumber
sumber
Penggunaan agregat yang sangat sederhana (setara dengan lipatan dalam bahasa lain):
Satu-satunya downside adalah bahwa properti diakses dua kali per elemen urutan, yang mungkin mahal. Itu sulit diperbaiki.
sumber
Berikut ini adalah solusi yang lebih umum. Ini pada dasarnya melakukan hal yang sama (dalam urutan O (N)) tetapi pada setiap jenis IEnumberable dan dapat dicampur dengan jenis yang selektor propertinya dapat mengembalikan nol.
Tes:
sumber
Sunting lagi:
Maaf. Selain kehilangan nullable saya melihat fungsi yang salah,
Min <(Dari <(TSource, TResult>)>) (IEnumerable <(Dari <(TSource>)>), Func <(Dari <(TSource, TResult>)>)) mengembalikan tipe hasil seperti yang Anda katakan.
Saya akan mengatakan salah satu solusi yang mungkin adalah dengan mengimplementasikan IComparable dan menggunakan Min <(Of <(TSource>)>) (IEnumerable <(Of <(TSource>)>))) , yang benar-benar mengembalikan elemen dari IEnumerable. Tentu saja, itu tidak membantu Anda jika Anda tidak dapat memodifikasi elemen. Saya menemukan desain MS agak aneh di sini.
Tentu saja, Anda selalu dapat melakukan for for loop jika perlu, atau menggunakan implementasi MoreLINQ yang diberikan Jon Skeet.
sumber
Implementasi lain, yang dapat bekerja dengan kunci pemilih yang dapat dibatalkan, dan untuk koleksi jenis referensi mengembalikan nol jika tidak ada elemen yang cocok ditemukan. Ini bisa membantu kemudian memproses hasil basis data misalnya.
Contoh:
sumber
Saya sendiri sedang mencari sesuatu yang serupa, lebih disukai tanpa menggunakan perpustakaan atau mengurutkan seluruh daftar. Solusi saya akhirnya mirip dengan pertanyaan itu sendiri, hanya sedikit disederhanakan.
sumber
var min = People.Min(...); var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == min...
Kalau tidak, itu mendapatkan min berulang kali sampai menemukan yang Anda cari.