如何使用LINQ選擇具備最小或最大屬性值的對象

我有一個具備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);

仍是有一種更精簡的方法? 對象


#1樓

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();
}

#2樓

無需額外包裝的解決方案: 排序

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


#3樓

我一直在尋找相似的東西,最好不要使用庫或對整個列表進行排序。 個人解決方案最終相似於問題自己,只是簡化了一點。 it

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

#4樓

如下是更通用的解決方案。 它基本上執行相同的操做(以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

#5樓

所以,您要使用ArgMinArgMax 。 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),它首先比較x1x2 ,而後比較y1y2 。 這就是爲何能夠在這些類型上使用內置的.Min的緣由。

並且因爲匿名類型和值元組都是值類型,所以它們都應該很是有效。

注意

在上面的ArgMin實現中,爲簡單起見,我假定DateOfBirth類型爲DateTime 。 原始問題要求排除那些具備空DateOfBirth字段的條目:

空的DateOfBirth值設置爲DateTime.MaxValue以便將它們排除在Min考慮以外(假設至少一個具備指定的DOB)。

能夠經過預過濾來實現

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

所以,對於實現ArgMinArgMax的問題ArgMax

筆記2

上面的方法有一個告誡,當有兩個具備相同最小值的實例時, Min()實現將嘗試將這些實例做爲平局決勝。 可是,若是實例的類未實現IComparable ,則將引起運行時錯誤:

至少一個對象必須實現IComparable

幸運的是,這仍然能夠解決得很乾淨。 這個想法是將一個明顯的「 ID」與充當明確的決勝局的每一個條目相關聯。 咱們能夠爲每一個條目使用增量ID。 仍以人口年齡爲例:

var youngest = Enumerable.Range(0, int.MaxValue)
               .Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;
相關文章
相關標籤/搜索