如何让LINQ返回具有给定属性的最大值的对象?

Fra*_*ank 130 c# linq linq-to-objects

如果我有一个类似于以下的类:

public class Item
{
    public int ClientID { get; set; }
    public int ID { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这些物品的集合......

List<Item> items = getItems();
Run Code Online (Sandbox Code Playgroud)

如何使用LINQ返回具有最高ID的单个"Item"对象?

如果我这样做:

items.Select(i => i.ID).Max(); 
Run Code Online (Sandbox Code Playgroud)

我只会得到最高的ID,当我真正想要返回的是具有最高ID的Item对象本身?我希望它返回一个"Item"对象,而不是int.

Sea*_*ard 140

这将只循环一次.

Item biggest = items.Aggregate((i1,i2) => i1.ID > i2.ID ? i1 : i2);
Run Code Online (Sandbox Code Playgroud)

谢谢尼克 - 这是证明

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<Item> items1 = new List<Item>()
        {
            new Item(){ ClientID = 1, ID = 1},
            new Item(){ ClientID = 2, ID = 2},
            new Item(){ ClientID = 3, ID = 3},
            new Item(){ ClientID = 4, ID = 4},
        };
        Item biggest1 = items1.Aggregate((i1, i2) => i1.ID > i2.ID ? i1 : i2);

        Console.WriteLine(biggest1.ID);
        Console.ReadKey();
    }


}

public class Item
{
    public int ClientID { get; set; }
    public int ID { get; set; }
}  
Run Code Online (Sandbox Code Playgroud)

重新排列列表并获得相同的结果

  • @ruffin我会使用items1.Any().这样你就不会迭代整个集合. (3认同)
  • 那么,`Item itemBig = items1.Count() &gt; 0 ?items1.Aggregate((i1, i2) =&gt; i1.ID &gt; i2.ID ? i1 : i2) : null;` (2认同)

Cod*_*ism 54

.OrderByDescending(i=>i.id).Take(1)
Run Code Online (Sandbox Code Playgroud)

关于性能问题,这种方法很可能在理论上比线性方法慢.然而,实际上,大多数情况下,我们并没有处理足够大的数据集来做出任何改变.

如果表现是一个主要问题,西雅图伦纳德的回答应该给你线性时间复杂度.或者,您也可以考虑从不同的数据结构开始,该结构在固定时间返回最大值项.

  • `.OrderByDescending(i => i.id).First()`将返回对象本身,而不是具有1项的枚举. (25认同)
  • 工作,但它是nlogn而不是线性时间. (13认同)
  • @Dortimer 如果这是一个有效的错误,那么这与更安全相反。应该引起每个人的注意。“错误隐藏”是一个坏习惯(一种反模式)。当我期望一个元素时,我偶尔仍然使用 FirstOrDefault(),但它只是为了能够产生比默认值更多信息的异常消息 (7认同)
  • tzaman:*理论上*,LINQ系统可以识别"orderby().take()"模式并使用线性时间算法 - 但你可能没有. (4认同)
  • 我知道这太旧了,但 @stevecook `.FirstOrDefault()` 可能是更好的选择,因为它不太容易出现问题。 (2认同)

Nic*_*sen 31

int max = items.Max(i => i.ID);
var item = items.First(x => x.ID == max);
Run Code Online (Sandbox Code Playgroud)

这假设当然物品集合中有元素.

  • 这个答案做了不必要的工作 该列表在第一次调用"Max"时完全迭代.下一次调用`First`将对列表执行另一次迭代以找到该元素. (3认同)

tza*_*man 29

使用MaxBymorelinq项目:

items.MaxBy(i => i.ID);
Run Code Online (Sandbox Code Playgroud)

  • @Reed:我猜你现在已经找到了原因......但是对于其他读者:Max返回最大值,而不是包含*最大值的项目*.请注意,"MaxBy"也在Reactive Extensions框架中的System.Interactive中. (8认同)
  • @BlueRaja:`morelinq`有很多有用的功能我几乎每个项目都会把它扔进去.:)此外,我发现`MaxBy`在意图上要比相应的`Aggregate`语法更清晰 - 减少精神分析时间总是有益于以后的行.当然,任何有实际经验的人都会认识到折叠的速度,但不是每个人都有.最后,NickLarsen的解决方案进行了两次传递(如果存在多个最大值,则会出现问题). (2认同)

Pau*_*rds 6

这是源自@Seattle Leonard 的回答的扩展方法:

 public static T GetMax<T,U>(this IEnumerable<T> data, Func<T,U> f) where U:IComparable
 {
     return data.Aggregate((i1, i2) => f(i1).CompareTo(f(i2))>0 ? i1 : i2);
 }
Run Code Online (Sandbox Code Playgroud)


Tom*_*cek 5

如果您不想使用MoreLINQ并希望获得线性时间,您还可以使用Aggregate:

var maxItem = 
  items.Aggregate(
    new { Max = Int32.MinValue, Item = (Item)null },
    (state, el) => (el.ID > state.Max) 
      ? new { Max = el.ID, Item = el } : state).Item;
Run Code Online (Sandbox Code Playgroud)

这会记住匿名类型中的当前最大元素(Item)和当前最大值(Item).然后你只需选择该Item物业.这确实有点难看,您可以将其包装到MaxBy扩展方法中以获得与MoreLINQ相同的内容:

public static T MaxBy(this IEnumerable<T> items, Func<T, int> f) {
  return items.Aggregate(
    new { Max = Int32.MinValue, Item = default(T) },
    (state, el) => {
      var current = f(el.ID);
      if (current > state.Max) 
        return new { Max = current, Item = el };
      else 
        return state; 
    }).Item;
}
Run Code Online (Sandbox Code Playgroud)


oll*_*llb 5

或者您可以编写自己的扩展方法:

static partial class Extensions
{
    public static T WhereMax<T, U>(this IEnumerable<T> items, Func<T, U> selector)
    {
        if (!items.Any())
        {
            throw new InvalidOperationException("Empty input sequence");
        }

        var comparer = Comparer<U>.Default;
        T   maxItem  = items.First();
        U   maxValue = selector(maxItem);

        foreach (T item in items.Skip(1))
        {
            // Get the value of the item and compare it to the current max.
            U value = selector(item);
            if (comparer.Compare(value, maxValue) > 0)
            {
                maxValue = value;
                maxItem  = item;
            }
        }

        return maxItem;
    }
}
Run Code Online (Sandbox Code Playgroud)