2009-05-27 18 views
344

NULL ile alınabilen bir DateOfBirth özelliğine sahip bir Kişi nesnesine sahibim. En erken/en küçük DateOfBirth değerine sahip olan Kişi nesneleri listesini sorgulamak için LINQ kullanmanın bir yolu var mı. (En az bir belirtilen bir DOB vardır varsayarak)Minimum veya maksimum özellik değeri olan nesneyi seçmek için LINQ nasıl kullanılır

var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue)); 

Boş DateOfBirth değerleri Min dikkate dışına ekarte etmek için DateTime.MaxValue ayarlanır:

İşte başladı budur.

Ancak, benim için olan tüm şey, firstBornDate değerini bir DateTime değerine ayarlamaktır. Benim elde etmek istediğim, onunla eşleşen Kişi nesnesi.

var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate); 

Veya bunu yapmanın bir yalın bir yolu vardır: Öyle gibi ikinci sorgu yazmak gerekiyor mu?

+19

Örneğinize bir yorum: Muhtemelen burada Single kullanmamalısınız. İki Kişi aynı DateOfBirth – Niki

+1

'a sahip olsaydı bir istisna atardı. Neredeyse aynı kopyaya sahip olan http://stackoverflow.com/questions/2736236/how-to-use-linq-to-find-the-minimum adresine bakın. özlü örnekler. – goodeye

+4

Ne kadar basit ve kullanışlı bir özellik. MinBy standart kütüphanede olmalı. Microsoft'a bir çekme isteği göndermeliyiz. Https://github.com/dotnet/corefx –

cevap

240
People.Aggregate((curMin, x) => (curMin == null || (x.DateOfBirth ?? DateTime.MaxValue) < 
    curMin.DateOfBirth ? x : curMin)) 
+10

Muhtemelen IComparable'ı uygulamaktan ve Min (veya for döngüsü) kullanmaktan biraz daha yavaştır. Ama bir O (n) linqy çözümü için +1. –

+2

Ayrıca,

+3

@Matthew Ah evet, düzeltildi –

0

Tekrar EDIT:

Üzgünüz. Nullamı kaçırmanın yanı sıra, yanlış fonksiyona baktığımda,

Min<(Of <(TSource, TResult>)>)(IEnumerable<(Of <(TSource>)>), Func<(Of <(TSource, TResult>)>)), dediğiniz gibi sonuç türünü döndürür.

Olası bir çözüm, IComparable'ı uygulamak ve IEnumerable öğesinden gerçekten bir öğe döndüren Min<(Of <(TSource>)>)(IEnumerable<(Of <(TSource>)>)) kullanmaktır. Tabii ki, öğeyi değiştiremezseniz bu size yardımcı olmaz. MS'nin tasarımını biraz garip buluyorum.

Elbette, ihtiyacınız varsa, bir for döngüsünü her zaman yapabilirsiniz veya Jon Skeet'in verdiği MoreLINQ uygulamasını kullanın.

180

Maalesef bunu yapmak için yerleşik bir yöntem yoktur.

PM> Yükle-Package morelinq

var firstBorn = People.MinBy(p => p.DateOfBirth ?? DateTime.MaxValue); 

Alternatif olarak, biz MinBy.cs yılında, MoreLINQ yılında var uygulanmasını kullanabilirsiniz. (Bir elbette MaxBy gelen var.) İşte bunun bağırsaklar şunlardır: Bu dizi boşsa bir özel durum oluşturur, ve minimal değere eğer ile ilk elemanı döneceğini

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, 
    Func<TSource, TKey> selector) 
{ 
    return source.MinBy(selector, null); 
} 

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, 
    Func<TSource, TKey> selector, IComparer<TKey> comparer) 
{ 
    if (source == null) throw new ArgumentNullException("source"); 
    if (selector == null) throw new ArgumentNullException("selector"); 
    comparer = comparer ?? Comparer<TKey>.Default; 

    using (var sourceIterator = source.GetEnumerator()) 
    { 
     if (!sourceIterator.MoveNext()) 
     { 
      throw new InvalidOperationException("Sequence contains no elements"); 
     } 
     var min = sourceIterator.Current; 
     var minKey = selector(min); 
     while (sourceIterator.MoveNext()) 
     { 
      var candidate = sourceIterator.Current; 
      var candidateProjected = selector(candidate); 
      if (comparer.Compare(candidateProjected, minKey) < 0) 
      { 
       min = candidate; 
       minKey = candidateProjected; 
      } 
     } 
     return min; 
    } 
} 

Not Birden fazla var.

+1

Ienumerator + foreach ile birlikte – ggf31416

+3

Döngüden önce MoveNext() öğesine yapılan ilk çağrı nedeniyle bunu kolayca yapamazsınız. Alternatifler var ama daha çok IMO var. –

+0

Güzel yöntem. Gerçek Linq paketinde olmalıdır. Neden boş sekanslara atıyorsun? Bu durumda bir numara döndüren (http://msdn.microsoft.com/en-us/library/bb352408.aspx) gerçekten döndüren Min aşırı yük null değerini döndürür. –

92

NOT: OP, veri kaynağının ne olduğundan bahsetmediğinden ve herhangi bir varsayımda bulunmamamız gerektiğinden, bu yanıtı eksiksiz olarak dahil ediyorum.

var oldest = People.OrderBy(p => p.DateOfBirth ?? DateTime.MaxValue).First(); 

GÜNCELLEME:: is

Bu sorgu doğru cevap verdiğini, ancak hangi verilerin yapısına People bağlı People içinde tüm öğeleri sıralamak gerekebilir çünkü yavaş olabilir ben Aslında Bu çözümü "naif" olarak adlandırmamalı, ancak kullanıcının neyi sorguladığını bilmesi gerekiyor.Bu çözümün "yavaşlığı" temeldeki verilere bağlıdır. Bu bir dizi veya List<T> ise, LINQ to Objects seçeneğinin, ilk öğeyi seçmeden önce ilk önce tüm koleksiyonu sıralamaktan başka seçeneği yoktur. Bu durumda önerilen diğer çözümden daha yavaş olacaktır. Ancak, bu bir SQL Server için LINQ ve DateOfBirth dizinli bir sütun ise, SQL Server tüm satırları sıralamak yerine dizini kullanır. Diğer özel IEnumerable<T> uygulamaları, dizinlerin tamamını kullanabilir (bkz. i4o: Indexed LINQ veya nesne veritabanı db4o) ve bu çözümü tüm koleksiyonu bir kez yinelemeye ihtiyaç duyan Aggregate() veya MaxBy()/10'dan daha hızlı bir şekilde yapabilirsiniz. Aslında, LINQ to Objects (teoride), OrderBy() içinde SortedList<T> gibi sıralı koleksiyonlar için özel durumlara sahip olabilir, ancak bildiğim kadarıyla değil.

+1

Birisi bunu zaten yayınladı, ancak ne kadar yavaş (ve alan tüketen) olduğunu açıkladıktan sonra görünüşte silinmişti (O (n log n) hızı en iyi O (n) ile min. :) –

+0

evet, bu yüzden naif çözüm olma konusundaki uyarılarım :) ancak yine de basittir ve bazı durumlarda kullanılabilir (küçük koleksiyonlar veya DateOfBirth bir dizinlenmiş DB sütunuysa) – Lucas

+0

başka bir özel durum (ya da yoktur) sıralama bilgisini kullanmak ve önce sıralama yapmadan en düşük değeri araştırmak mümkün olacaktır. –

57
People.OrderBy(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue)).First() 

beklenen şeyi yapmak

+0

En çok beğeniyorum, thx! –

+1

Bu harika! OrderByDesending (...) ile kullandım. Linq projelendirme durumumda (1) al. –

+1

Bu, O (N) zamanını aşan ve ayrıca O (N) belleğini kullanan sıralama kullanır. Veri kaynağı hakkında çok şey bildiğimizi varsayan –

4
public class Foo { 
    public int bar; 
    public int stuff; 
}; 

void Main() 
{ 
    List<Foo> fooList = new List<Foo>(){ 
    new Foo(){bar=1,stuff=2}, 
    new Foo(){bar=3,stuff=4}, 
    new Foo(){bar=2,stuff=3}}; 

    Foo result = fooList.Aggregate((u,v) => u.bar < v.bar ? u: v); 
    result.Dump(); 
} 
1

kontrol İn hile yapmak, ancak bu şart misiniz:

var itemWithMaxValue = SomeListOfClass.OrderByDescending(i => i.SomeFloat).FirstOrDefault(); 

ve min:

var itemWithMinValue = SomeListOfClass.OrderByDescending(i => i.SomeFloat).LastOrDefault(); 
0

Ben kendiminkine benzer bir şey arıyorum, tercihen bir kütüphane kullanmadan veya tüm listeyi sıralar. Benim çözümüm, sorunun kendisine benzer bir şekilde sona erdi, sadece biraz basitleştirildi.

var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == People.Min(p2 => p2.DateOfBirth)); 
+0

Linq ifadesinden önce min almak için çok daha verimli olmaz mıydı? 'var min = Kişi.Min (...); var firstBorn = People.FirstOrDefault (p => p.DateOfBirth == dak ... 'Aksi halde, aradığınız birini bulana kadar en az bir kez tekrarlanıyor. – Nieminen

1

sadece Varlık framework 6.0.0> için bu kontrol ettiniz: Bu yapılabilir:

var MaxValue = dbContext.YourDataClass.Select(x => x.ColumnToFindMaxValueFrom).Max(); 
var MinValue = dbContext.YourDataClass.Select(x => x.ColumnToFindMinValueFrom).Min(); 
+0

Bana zaman kazandırdın !!!! – Patricia

2

aşağıdaki daha genel bir çözümdür. Esasen aynı şeyi (O (N) düzeninde) yapar, ancak herhangi bir IEnumberable türünde ve özellik seçicilerinin boş dönebileceği türlerle karışabilir.

public static class LinqExtensions 
{ 
    public static T MinBy<T>(this IEnumerable<T> source, Func<T, IComparable> selector) 
    { 
     if (source == null) 
     { 
      throw new ArgumentNullException(nameof(source)); 
     } 
     if (selector == null) 
     { 
      throw new ArgumentNullException(nameof(selector)); 
     } 
     return source.Aggregate((min, cur) => 
     { 
      if (min == null) 
      { 
       return cur; 
      } 
      var minComparer = selector(min); 
      if (minComparer == null) 
      { 
       return cur; 
      } 
      var curComparer = selector(cur); 
      if (curComparer == null) 
      { 
       return min; 
      } 
      return minComparer.CompareTo(curComparer) > 0 ? cur : min; 
     }); 
    } 
} 

Testler:

var nullableInts = new int?[] {5, null, 1, 4, 0, 3, null, 1}; 
Assert.AreEqual(0, nullableInts.MinBy(i => i));//should pass 
1

Yani ArgMin veya ArgMax soruyor. C#, bunlar için yerleşik bir API'ye sahip değil.

Bunu yapmak için temiz ve verimli (O (n) zamanda) bir yol arıyordum.

C# 7.0 için ve value tuple bu destekler üzerinde: Ve ben bir tane buldum C# sürümü için

var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2; 

7.0 öncesinde, anonymous type yerine kullanılabilir:

var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl; 

Her ikisi de değer tuple ve anonim türlerin mantıklı varsayılan karşılaştırıcıları olduğu için çalışırlar: (x1, y1) ve (x2, y2) için, ilk olarak x1'a karşılık x2, daha sonra y1 vs y2. Bu yüzden dahili .Min bu tiplerde kullanılabilir.

Hem anonim hem de değer tuple değerleri olduğundan, her ikisi de çok verimli olmalıdır.Benim yukarıda ArgMin uygulamalarda

NOT

Ben sadelik ve netlik için türünü DateTime almaya DateOfBirth üstlendi. Orijinal soru boş DateOfBirth alanına sahip olanlar girdileri dışlamak için sorar:

Boş DateOfBirth değerleri Min dikkate dışına ekarte etmek için DateTime.MaxValue ayarlanır (belirli bir DOB en az birini gelmiştir varsayılarak). Bu bir ön filtreleme

people.Where(p => p.DateOfBirth.HasValue) 

ile elde edilebilir

Yani ArgMin veya ArgMax uygulanmasının söz için önemsiz.

İlgili konular