我有一个具有Nullable DateOfBirth属性的Person对象.有没有办法使用LINQ查询具有最早/最小DateOfBirth值的Person对象列表.
这是我开始的:
var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));
Run Code Online (Sandbox Code Playgroud)
Null DateOfBirth值设置为DateTime.MaxValue,以便将它们排除在Min考虑范围之外(假设至少有一个具有指定的DOB).
但对我来说,所有这一切都是将firstBornDate设置为DateTime值.我想得到的是与之匹配的Person对象.我是否需要编写第二个查询:
var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);
Run Code Online (Sandbox Code Playgroud)
或者有更简洁的方法吗?
Ana*_*tts 282
People.Aggregate((curMin, x) => (curMin == null || (x.DateOfBirth ?? DateTime.MaxValue) <
curMin.DateOfBirth ? x : curMin))
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 215
不幸的是,没有内置的方法来做到这一点.
PM> Install-Package morelinq
var firstBorn = People.MinBy(p => p.DateOfBirth ?? DateTime.MaxValue);
Run Code Online (Sandbox Code Playgroud)
另外,您也可以使用我们已经有了在实施MoreLINQ,在MinBy.cs.(MaxBy当然有一个相应的.)以下是它的内容:
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 ?? 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;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,如果序列为空,这将抛出异常,如果有多个,则返回具有最小值的第一个元素.
Luc*_*cas 123
注意:由于OP没有提到数据源是什么而且我们不应该做出任何假设,因此我将此答案包含在内以便完整.
此查询提供了正确的答案,但可能会更慢,因为它可能必须对所有项目进行排序People,具体取决于数据结构People:
var oldest = People.OrderBy(p => p.DateOfBirth ?? DateTime.MaxValue).First();
Run Code Online (Sandbox Code Playgroud)
更新:实际上我不应该把这个解决方案称为"天真",但用户确实需要知道他正在查询的内容.该解决方案的"缓慢"取决于底层数据.如果这是一个数组或List<T>,那么LINQ to Objects别无选择,只能在选择第一个项目之前先对整个集合进行排序.在这种情况下,它将比建议的其他解决方案慢.但是,如果这是一个LINQ to SQL表并且DateOfBirth是索引列,则SQL Server将使用索引而不是对所有行进行排序.其他定制IEnumerable<T>的实现也可以利用指标(见i4o的:LINQ索引,或者对象数据库db4o的),使这个解决方案的速度比Aggregate()或MaxBy()/ MinBy()这就需要一次遍历整个集合.事实上,LINQ to Objects可以(在理论上)OrderBy()为排序集合制作特殊情况SortedList<T>,但据我所知,它没有.
Run*_* FS 60
People.OrderBy(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue)).First()
Run Code Online (Sandbox Code Playgroud)
会做的伎俩
KFL*_*KFL 26
所以你要求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(.)
Run Code Online (Sandbox Code Playgroud)
特别是,使用原始问题中的示例:
对于支持值元组的 C#7.0及更高版本:
var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;
Run Code Online (Sandbox Code Playgroud)
对于7.0之前的C#版本,可以使用匿名类型:
var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;
Run Code Online (Sandbox Code Playgroud)
它们起作用,因为值元组和匿名类型都有合理的默认比较器:对于(x1,y1)和(x2,y2),它首先比较x1vs x2,然后y1比较y2.这就是内置.Min可以用于这些类型的原因.
由于匿名类型和值元组都是值类型,因此它们应该非常有效.
注意
在上面的ArgMin实现中,我假设DateOfBirth采用类型DateTime来简化和清晰.原始问题要求使用空DateOfBirth字段排除这些条目:
Null DateOfBirth值设置为DateTime.MaxValue,以便将它们排除在Min考虑范围之外(假设至少有一个具有指定的DOB).
它可以通过预过滤实现
people.Where(p => p.DateOfBirth.HasValue)
Run Code Online (Sandbox Code Playgroud)
因此,实施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;
Run Code Online (Sandbox Code Playgroud)
And*_*rew 18
没有额外包装的解决方案:
var min = lst.OrderBy(i => i.StartDate).FirstOrDefault();
var max = lst.OrderBy(i => i.StartDate).LastOrDefault();
Run Code Online (Sandbox Code Playgroud)
你也可以把它包装成扩展名:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下:
var min = lst.MinBy(i => i.StartDate);
var max = lst.MaxBy(i => i.StartDate);
Run Code Online (Sandbox Code Playgroud)
顺便说一句...... O(n ^ 2)不是最好的解决方案.保罗贝茨给了我最好的解决方案.但我仍然是LINQ解决方案,它比其他解决方案更简单,更简洁.
从 .Net 6(预览版 7)或更高版本开始,有新的内置方法Enumerable.MaxBy和Enumerable.MinBy来实现此目的。
var lastBorn = people.MaxBy(p => p.DateOfBirth);
var firstBorn = people.MinBy(p => p.DateOfBirth);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
238090 次 |
| 最近记录: |