我有一個具備Nullable DateOfBirth屬性的Person對象。 有沒有一種方法可使用LINQ來查詢Person對象列表中最先/最小的DateOfBirth值。 測試
這是我開始的: this
var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));
空的DateOfBirth值設置爲DateTime.MaxValue以便將它們排除在Min考慮以外(假設至少一個具備指定的DOB)。 spa
可是對我來講,全部要作的就是將firstBornDate設置爲DateTime值。 我想要獲得的是與此匹配的Person對象。 我是否須要這樣編寫第二個查詢: code
var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);
仍是有一種更精簡的方法? 對象
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(); }
無需額外包裝的解決方案: 排序
var min = lst.OrderBy(i => i.StartDate).FirstOrDefault(); var max = lst.OrderBy(i => i.StartDate).LastOrDefault();
您也能夠將其包裝到擴展中: ip
public static class LinqExtensions { public static T MinBy<T, TProp>(this IEnumerable<T> source, Func<T, TProp> propSelector) { return source.OrderBy(propSelector).FirstOrDefault(); } public static T MaxBy<T, TProp>(this IEnumerable<T> source, Func<T, TProp> propSelector) { return source.OrderBy(propSelector).LastOrDefault(); } }
在這種狀況下: ci
var min = lst.MinBy(i => i.StartDate); var max = lst.MaxBy(i => i.StartDate);
順便說一下... O(n ^ 2)不是最佳解決方案。 保羅·貝茨 ( Paul Betts)提供了比我強的解決方案。 可是我仍然是LINQ解決方案,比這裏的其餘解決方案更簡單,更短。 get
我一直在尋找相似的東西,最好不要使用庫或對整個列表進行排序。 個人解決方案最終相似於問題自己,只是簡化了一點。 it
var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == People.Min(p2 => p2.DateOfBirth));
如下是更通用的解決方案。 它基本上執行相同的操做(以O(N)順序),可是在任何IEnumberable類型上,而且能夠與屬性選擇器能夠返回null的類型混合使用。
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; }); } }
測試:
var nullableInts = new int?[] {5, null, 1, 4, 0, 3, null, 1}; Assert.AreEqual(0, nullableInts.MinBy(i => i));//should pass
所以,您要使用ArgMin
或ArgMax
。 C#沒有針對這些的內置API。
我一直在尋找一種乾淨有效的方法(及時(O [n))來作到這一點。 我想我發現了一個:
此模式的通常形式是:
var min = data.Select(x => (key(x), x)).Min().Item2; ^ ^ ^ the sorting key | take the associated original item Min by key(.)
特別地,使用原始問題中的示例:
對於支持值元組的 C#7.0及更高版本:
var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;
對於7.0以前的C#版本,能夠改用匿名類型 :
var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;
之因此起做用,是由於值元組和匿名類型都具備明智的默認比較器:對於(x1,y1)和(x2,y2),它首先比較x1
與x2
,而後比較y1
與y2
。 這就是爲何能夠在這些類型上使用內置的.Min
的緣由。
並且因爲匿名類型和值元組都是值類型,所以它們都應該很是有效。
注意
在上面的ArgMin
實現中,爲簡單起見,我假定DateOfBirth
類型爲DateTime
。 原始問題要求排除那些具備空DateOfBirth
字段的條目:
空的DateOfBirth值設置爲DateTime.MaxValue以便將它們排除在Min考慮以外(假設至少一個具備指定的DOB)。
能夠經過預過濾來實現
people.Where(p => p.DateOfBirth.HasValue)
所以,對於實現ArgMin
或ArgMax
的問題ArgMax
。
筆記2
上面的方法有一個告誡,當有兩個具備相同最小值的實例時, Min()
實現將嘗試將這些實例做爲平局決勝。 可是,若是實例的類未實現IComparable
,則將引起運行時錯誤:
至少一個對象必須實現IComparable
幸運的是,這仍然能夠解決得很乾淨。 這個想法是將一個明顯的「 ID」與充當明確的決勝局的每一個條目相關聯。 咱們能夠爲每一個條目使用增量ID。 仍以人口年齡爲例:
var youngest = Enumerable.Range(0, int.MaxValue) .Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;