我有一个具有Nullable DateOfBirth属性的Person对象。是否有一种方法可以使用LINQ来查询Person对象列表中最早/最小的DateOfBirth值?

这是我的开场白:

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

Null DateOfBirth值被设置为DateTime。MaxValue,以便将它们排除在Min考虑之外(假设至少有一个具有指定的DOB)。

但是所有这些对我来说都是将firstBornDate设置为DateTime值。我想要的是与之匹配的Person对象。我是否需要像这样写第二个查询:

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

或者有没有更精简的方法?


当前回答

聚合的简单使用(相当于其他语言中的折叠):

var firstBorn = People.Aggregate((min, x) => x.DateOfBirth < min.DateOfBirth ? x : min);

唯一的缺点是每个sequence元素访问属性两次,这可能会很昂贵。这很难解决。

其他回答

我自己也在寻找类似的东西,最好不使用库或对整个列表进行排序。我的解决方案与问题本身相似,只是简化了一点。

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

下面是更通用的解决方案。它本质上做相同的事情(以O(N)顺序),但对任何IEnumerable类型,并且可以与属性选择器可以返回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

不幸的是,没有内置的方法来做到这一点,但它很容易为自己实现。以下是它的核心内容:

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

使用示例:

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

请注意,如果序列为空,将抛出异常,如果有多个元素,则返回第一个具有最小值的元素。

或者,你也可以在MinBy.cs中使用MoreLINQ中的实现。(当然,有一个相应的MaxBy。)

通过包管理器控制台安装:

PM>安装包morelinq

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

通过IEnumerable上的扩展函数返回对象和找到的最小值的方法。它接受一个可以对集合中的对象执行任何操作的Func:

public static (double min, T obj) tMin<T>(this IEnumerable<T> ienum, 
            Func<T, double> aFunc)
        {
            var okNull = default(T);
            if (okNull != null)
                throw new ApplicationException("object passed to Min not nullable");

            (double aMin, T okObj) best = (double.MaxValue, okNull);
            foreach (T obj in ienum)
            {
                double q = aFunc(obj);
                if (q < best.aMin)
                    best = (q, obj);
            }
            return (best);
        }

例子,对象是一个机场,我们想找到最近的机场到给定的(纬度,经度)。机场有纬度(纬度、纬度)功能。

(double okDist, Airport best) greatestPort = airPorts.tMin(x => x.dist(okLat, okLon));