我有一个具有Nullable DateOfBirth属性的Person对象。 有没有一种方法可以使用LINQ来查询Person对象列表中最早/最小的DateOfBirth值。
这是我开始的:
var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));
空的DateOfBirth值设置为DateTime.MaxValue以便将它们排除在Min考虑之外(假设至少一个具有指定的DOB)。
但是对我来说,所有要做的就是将firstBornDate设置为DateTime值。 我想要得到的是与此匹配的Person对象。 我是否需要这样编写第二个查询:
var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);
还是有一种更精简的方法?
#1楼
public class Foo {
public int bar;
public int stuff;
};
void Main()
{
List fooList = new List(){
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();
您也可以将其包装到扩展中:
public static class LinqExtensions
{
public static T MinBy(this IEnumerable source, Func propSelector)
{
return source.OrderBy(propSelector).FirstOrDefault();
}
public static T MaxBy(this IEnumerable source, Func propSelector)
{
return source.OrderBy(propSelector).LastOrDefault();
}
}
在这种情况下:
var min = lst.MinBy(i => i.StartDate);
var max = lst.MaxBy(i => i.StartDate);
顺便说一下... O(n ^ 2)不是最佳解决方案。 保罗·贝茨 ( Paul Betts)提供了比我强的解决方案。 但是我仍然是LINQ解决方案,比这里的其他解决方案更简单,更短。
#3楼
我一直在寻找类似的东西,最好不要使用库或对整个列表进行排序。 我的解决方案最终类似于问题本身,只是简化了一点。
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(this IEnumerable source, Func 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楼
因此,您要使用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;