所以你要求ArgMin或ArgMax。c#没有针对这些的内置API。
我一直在寻找一种干净高效(O(n) in time)的方法来做到这一点。我想我找到了一个:
这种模式的一般形式是:
var min = data.Select(x => (key(x), x)).Min().Item2;
^ ^ ^
the sorting key | take the associated original item
Min by key(.)
特别地,用原题中的例子:
对于支持value tuple的c# 7.0及以上版本:
var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;
对于7.0之前的c#版本,可以使用匿名类型:
var youngest = people.Select(p => new {age = p.DateOfBirth, ppl = p}).Min().ppl;
它们之所以有效,是因为value tuple和匿名类型都有合理的默认比较器:对于(x1, y1)和(x2, y2),它首先比较x1 vs x2,然后比较y1 vs y2。这就是内置的. min可以用于这些类型的原因。
由于匿名类型和值元组都是值类型,它们应该都非常有效。
NOTE
在我上面的ArgMin实现中,为了简单明了,我假设DateOfBirth采用DateTime类型。原来的问题要求排除那些DateOfBirth字段为空的条目:
Null DateOfBirth值被设置为DateTime。MaxValue,以便将它们排除在Min考虑之外(假设至少有一个具有指定的DOB)。
它可以通过预过滤来实现
people.Where(p => p.DateOfBirth.HasValue)
因此,实现ArgMin或ArgMax的问题无关紧要。
注2
上面的方法需要注意的是,当有两个实例具有相同的最小值时,min()实现将尝试比较实例作为分分秒秒。然而,如果实例的类没有实现IComparable,则会抛出一个运行时错误:
至少有一个对象必须实现IComparable
幸运的是,这个问题仍然可以很干净地解决。其思想是将一个距离“ID”与每个条目关联起来,作为明确的分割线。我们可以为每个条目使用增量ID。还是以人的年龄为例:
var youngest = Enumerable.Range(0, int.MaxValue)
.Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;