从 List<t> 中获取具有特定属性最大值的记录

5

可能是重复问题:
LINQ:如何在集合中对所有对象的属性执行.Max()并返回具有最大值的对象

我有以下类:

class Product
{
    public string ProductName { get; set; }
    public DateTime ActivationDate { get; set; }
}

然后我创建并填充了一个 List<Product>,我想要获取具有最新的 ActivationDateProductProductName

Product.Where(m => m.ActivationDate == Max(m.ActivationDate)).Select(n => n.ProductName)

Product.Max(m => m.ActivationDate).Select(n => n.ProductName)

但是这两种方法都不起作用。有没有人知道实现这个任务的方法?

1
列表有多大?编写MaxBy(或类似函数)非常容易,但对于小型列表来说不值得。对于大型列表,它是值得的。差别在于O(n)O(n^2)(或者也许是O(n*log(n)))。 - Marc Gravell
6个回答

9
你可以按照激活日期字段ActivationDateList<Product>进行OrderByDescending排序,然后使用FirstOrDefault()获取第一个元素。
Product.OrderByDescending(p => p.ActivationDate).FirstOrDefault();

如果需要更简单的版本,可以使用扩展方法。

MaxBy

Product.MaxBy(p => p.ActivationDate);

是的,MoreLinq 是这里的最佳选择。 - Rawling
这个被踩是因为这不是一个最优解吗? - V4Vendetta
@V4Vendetta 是的,由于编辑我现在撤销了我的反对票。 - Branko Dimitrijevic
是的,我也一样。话虽如此,这可能仍然比其他答案更有效率 :) - Rawling
是的。我刚刚取消了踩,MaxBy 对这个很完美。 - Tomas Grosup

4
如果您能做到这一点:
class Product : IComparable<Product>
{
    public string ProductName { get; set; }
    public DateTime ActivationDate { get; set; }

    public int CompareTo(Product other)
    {
        return this.ActivationDate.CompareTo(other.ActivationDate);
    }
}

然后就是这样:
var max = products.Max(p => p).ProductName;

从来没有想过这个。如果有一个接受IComparator的Max重载,就可以进行不同的比较,那将是很棒的。(顺便问一下,你最后一行是指.ProductName吗?) - Rawling
最佳解决方案。您可以省略lambda参数:var max = products.Max().ProductName - Herman Kan

2

让我们开始吧;对列表进行一次遍历:

public static TSource MaxBy<TSource,TValue>(
    this IEnumerable<TSource> source,
    Func<TSource,TValue> selector)
{
    using(var iter = source.GetEnumerator())
    {
        if (!iter.MoveNext())
            throw new InvalidOperationException("Empty sequence");
        var max = selector(iter.Current);
        var item = iter.Current;
        var comparer = Comparer<TValue>.Default;
        while(iter.MoveNext())
        {
            var tmp = selector(iter.Current);
            if(comparer.Compare(max, tmp) < 0)
            {
                item = iter.Current;
                max = tmp;
            }
        }
        return item;
    }
}

然后:

var maxObj = list.MaxBy(x => x.SomeProp);

相较于执行OrderBy等操作需要实际排序数据,这种方法更加高效,因为它只需要一次扫描即可完成。


我知道这个名称与MoreLinq中的名称相同,但我认为这很不幸。 重要的是,应该在名称中包含的内容是它是否返回具有最大值的第一个项目或最后一个项目。 - Tomas Grosup
1
或者它可以返回所有最大值的对象。 - Tomas Grosup
@Marc - 最后一个观点很有趣。如果LINQ执行选择排序,那么OrderBy.First本质上将与MaxBy执行相同的操作,但您随后可以选择下几个项目。 - Rawling
1
@TomasGrosup 这里是美妙的事情;如果你复制并粘贴它,我不会介意你改变名字;p - Marc Gravell
@Marc 没错 - 如果你只想要前几个,实现一个基于延迟执行的选择排序的 OrderBy 然后 .Take 所需数量会更有效率,而不是使用不可延迟的基于快速排序的 OrderBy - Rawling
显示剩余2条评论

1
如何编写一个名为Max的扩展函数,该函数在内部执行Branko Dimitrijevic提供的简单搜索逻辑?
/// <param name="comparer">Func<T current, T currentMax, long> </param>
    public static T Max<T>(this List<T> collection, Func<T, T, long> comparer) where T : class
    {
        T max_product = null;
        collection.ForEach(c =>
        {
            if (max_product == null || comparer(c, max_product) > 0)
                max_product = c;
        });

        return max_product;
    }

调用此函数的方式为:

string maxProductName = products.Max<Product>((currentProduct, currentMaxProduct) =>
        {
            // Basically any logic
            return currentMaxProduct.ActivationDate.CompareTo(currentProduct.ActivationDate);
        }).ProductName;

0

如果你只需要在一个地方使用,那么非LINQ解决方案就足够简单了,通用的MaxBy可能会过于复杂:

Product max_product = null;

foreach (var product in products) {
    if (max_product == null || max_product.ActivationDate < product.ActivationDate)
        max_product = product;
}

// Use `max_product`...

-2

试试这个

ProductList.Where(m => m.ActivationDate == ProductList.Max(pl => pl.ActivationDate)).FirstOrDefault().ProductName;

1
至少存储最大值一次,然后与其进行比较。否则,您将为列表中的每个项目循环一次列表。 - Rawling

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接