如何使用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;
相关文章
相关标签/搜索