所以你正在寻求 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;
对于 C# 版本 7.0 之前,可以使用匿名类型代替:
var youngest = people.Select(p => new {age = p.DateOfBirth, ppl = p}).Min().ppl;
他们之所以有效,是因为值元组和匿名类型都有明智的默认比较器:对于 (x1, y1) 和 (x2, y2),首先比较
x1
与
x2
,然后比较
y1
与
y2
。这就是为什么可以在这些类型上使用内置的
.Min
。
由于匿名类型和值元组都是值类型,它们应该都非常高效。
注意:
在我的上述实现中,我假设
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;
a.Min(x => x.foo);
- jackmottmax("find a word of maximal length in this sentence".split(), key=len)
会返回字符串'sentence'。而在C#中,"find a word of maximal length in this sentence".Split().Max(word => word.Length)
可以计算出任何单词的最大长度是8,但无法告诉你最长的单词是什么。 - Colonel Panic