LINQ: Come eseguire .Max () su una proprietà di tutti gli oggetti in una raccolta e restituire l’object con il valore massimo

Ho una lista di oggetti che hanno due proprietà int. L’elenco è l’output di un’altra query di linq. L’object:

public class DimensionPair { public int Height { get; set; } public int Width { get; set; } } 

Voglio trovare e restituire l’object nell’elenco che ha il valore di proprietà Height più grande.

Riesco a ottenere il valore più alto del valore Height ma non l’object stesso.

Posso farlo con Linq? Come?

Abbiamo un metodo di estensione per fare esattamente questo in MoreLINQ . Puoi vedere l’implementazione lì, ma fondamentalmente è un caso di iterare attraverso i dati, ricordando l’elemento massimo che abbiamo visto finora e il valore massimo che ha prodotto sotto la proiezione.

Nel tuo caso dovresti fare qualcosa come:

 var item = items.MaxBy(x => x.Height); 

Questo è meglio (IMO) di qualsiasi altra soluzione qui presentata oltre alla seconda soluzione di Mehrdad (che è fondamentalmente la stessa di MaxBy ):

  • È O (n) diverso dalla precedente risposta accettata che trova il valore massimo su ogni iterazione (rendendolo O (n ^ 2))
  • La soluzione per l’ordine è O (n log n)
  • Prendendo il valore Max e quindi trovando il primo elemento con quel valore è O (n), ma itera sulla sequenza due volte. Ove ansible, dovresti usare LINQ in modalità single-pass.
  • È molto più semplice da leggere e comprendere rispetto alla versione aggregata e valuta solo la proiezione una volta per elemento

Ciò richiederebbe un ordinamento (O (n log n)) ma è molto semplice e flessibile. Un altro vantaggio è la possibilità di utilizzarlo con LINQ to SQL:

 var maxObject = list.OrderByDescending(item => item.Height).First(); 

Nota che questo ha il vantaggio di enumerare la sequenza di list solo una volta. Mentre potrebbe non importare se la list sia una List che non cambia nel frattempo, potrebbe importare oggetti I IEnumerable arbitrari. Nulla garantisce che la sequenza non cambi in enumerazioni diverse, quindi i metodi che lo fanno più volte possono essere pericolosi (e inefficienti, a seconda della natura della sequenza). Tuttavia, è ancora una soluzione tutt’altro che ideale per sequenze di grandi dimensioni. Suggerisco di scrivere manualmente la propria estensione MaxObject se si dispone di un ampio set di elementi per essere in grado di farlo in un unico passaggio senza l’ordinamento e altre cose di sorta (O (n)):

 static class EnumerableExtensions { public static T MaxObject(this IEnumerable source, Func selector) where U : IComparable { if (source == null) throw new ArgumentNullException("source"); bool first = true; T maxObj = default(T); U maxKey = default(U); foreach (var item in source) { if (first) { maxObj = item; maxKey = selector(maxObj); first = false; } else { U currentKey = selector(item); if (currentKey.CompareTo(maxKey) > 0) { maxKey = currentKey; maxObj = item; } } } if (first) throw new InvalidOperationException("Sequence is empty."); return maxObj; } } 

e usalo con:

 var maxObject = list.MaxObject(item => item.Height); 

Fare un ordine e quindi selezionare il primo object sta perdendo un sacco di tempo per ordinare gli oggetti dopo il primo. Non ti interessa l’ordine di quelli.

Puoi invece utilizzare la funzione di aggregazione per selezionare l’elemento migliore in base a ciò che stai cercando.

 var maxHeight = dimensions .Aggregate((agg, next) => next.Height > agg.Height ? next : agg); var maxHeightAndWidth = dimensions .Aggregate((agg, next) => next.Height >= agg.Height && next.Width >= agg.Width ? next: agg); 

E perché non provi con questo ??? :

 var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height)); 

O più ottimizza:

 var itemMaxHeight = items.Max(y => y.Height); var itemsMax = items.Where(x => x.Height == itemMaxHeight); 

mmm?

Le risposte finora sono grandi! Ma vedo la necessità di una soluzione con i seguenti vincoli:

  1. LINQ semplice e conciso;
  2. O (n) complessità;
  3. Non valutare la proprietà più di una volta per elemento.

Ecco qui:

 public static T MaxBy(this IEnumerable en, Func evaluate) where R : IComparable { return en.Select(t => new Tuple(t, evaluate(t))) .Aggregate((max, next) => next.Item2.CompareTo(max.Item2) > 0 ? next : max).Item1; } public static T MinBy(this IEnumerable en, Func evaluate) where R : IComparable { return en.Select(t => new Tuple(t, evaluate(t))) .Aggregate((max, next) => next.Item2.CompareTo(max.Item2) < 0 ? next : max).Item1; } 

Uso:

 IEnumerable> list = new[] { new Tuple("other", 2), new Tuple("max", 4), new Tuple("min", 1), new Tuple("other", 3), }; Tuple min = list.MinBy(x => x.Item2); // "min", 1 Tuple max = list.MaxBy(x => x.Item2); // "max", 4 

Credo che l’ordinamento in base alla colonna in cui si desidera ottenere il valore massimo di MAX e che quindi si possa afferrare il primo dovrebbe funzionare. Tuttavia, se ci sono più oggetti con lo stesso valore MAX, verrà afferrato solo uno:

 private void Test() { test v1 = new test(); v1.Id = 12; test v2 = new test(); v2.Id = 12; test v3 = new test(); v3.Id = 12; List arr = new List(); arr.Add(v1); arr.Add(v2); arr.Add(v3); test max = arr.OrderByDescending(t => t.Id).First(); } class test { public int Id { get; set; } } 

In NHibernate (con NHibernate.Linq) puoi farlo come segue:

 return session.Query() .Single(a => a.Filter == filter && a.Id == session.Query() .Where(a2 => a2.Filter == filter) .Max(a2 => a2.Id)); 

Di seguito verrà generato il seguente SQL:

 select * from TableName foo where foo.Filter = 'Filter On String' and foo.Id = (select cast(max(bar.RowVersion) as INT) from TableName bar where bar.Name = 'Filter On String') 

Mi sembra abbastanza efficiente.

Sulla base della risposta iniziale di Cameron, ecco cosa ho appena aggiunto alla mia versione avanzata del FloatingWindowHost della libreria SilverFlow (copia da FloatingWindowHost.cs al codice sorgente http://clipflair.codeplex.com )

  public int MaxZIndex { get { return FloatingWindows.Aggregate(-1, (maxZIndex, window) => { int w = Canvas.GetZIndex(window); return (w > maxZIndex) ? w : maxZIndex; }); } } private void SetTopmost(UIElement element) { if (element == null) throw new ArgumentNullException("element"); Canvas.SetZIndex(element, MaxZIndex + 1); } 

Vale la pena notare per quanto riguarda il codice sopra che Canvas.ZIndex è una proprietà associata disponibile per UIElements in vari contenitori, non solo utilizzata quando è ospitata in un Canvas (vedere Controllo dell’ordine di rendering (ZOrder) in Silverlight senza utilizzare il controllo Canvas ). Immagino che si possa persino creare un metodo di estensione statica SetTopmost e SetBottomMost per UIElement facilmente adattando questo codice.

Puoi anche aggiornare la soluzione di Mehrdad Afshari riscrivendo il metodo di estensione a uno più veloce (e più bello):

 static class EnumerableExtensions { public static T MaxElement(this IEnumerable container, Func valuingFoo) where R : IComparable { var enumerator = container.GetEnumerator(); if (!enumerator.MoveNext()) throw new ArgumentException("Container is empty!"); var maxElem = enumerator.Current; var maxVal = valuingFoo(maxElem); while (enumerator.MoveNext()) { var currVal = valuingFoo(enumerator.Current); if (currVal.CompareTo(maxVal) > 0) { maxVal = currVal; maxElem = enumerator.Current; } } return maxElem; } } 

E poi basta usarlo:

 var maxObject = list.MaxElement(item => item.Height); 

Quel nome sarà chiaro alle persone che usano C ++ (perché lì c’è std :: max_element).