我有一个具备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;