Come eseguire .max () su una proprietà di tutti gli oggetti in una collezione e restituire l'oggetto con valore massimo [duplicare]

StackOverflow https://stackoverflow.com/questions/1101841

  •  12-09-2019
  •  | 
  •  

Domanda

    

Questa domanda ha già una risposta qui:

         

Ho una lista di oggetti che hanno due proprietà Int. La lista è l'uscita di un altro query LINQ. L'oggetto:

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

Voglio trovare e restituire l'oggetto nella lista che ha il più grande valore della proprietà Height.

posso riuscire ad ottenere il valore più alto del valore Height ma non l'oggetto stesso.

Posso fare questo con LINQ? Come?

È stato utile?

Soluzione

Abbiamo un metodo di estensione di fare esattamente questo MoreLINQ . Potete guardare l'attuazione lì, ma in fondo si tratta di un caso di iterazione attraverso i dati, ricordando l'elemento massimo che abbiamo visto finora e il valore massimo è prodotto sotto la proiezione.

Nel tuo caso si farebbe qualcosa di simile:

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

Questo è meglio (IMO) di una qualsiasi delle soluzioni presentate qui diverso seconda soluzione di Mehrdad (che è sostanzialmente la stessa di MaxBy):

  • E 'O (n) a differenza del risposta precedente accettato che trova il valore massimo su ogni iterazione (rendendolo O ( n ^ 2))
  • La soluzione ordinamento è O (n log n)
  • Prendendo il valore Max e poi trovare il primo elemento con quel valore è O (n), ma itera sopra la sequenza di due volte. Ove possibile, si dovrebbe usare LINQ in modo single-pass.
  • E 'molto più semplice da leggere e capire rispetto alla versione di aggregazione, e valuta solo la proiezione una volta per ogni elemento

Altri suggerimenti

Questo richiederebbe una sorta (O (n login n)), ma è molto semplice e flessibile. Un altro vantaggio è quello di poter utilizzare con LINQ to SQL:

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

Si noti che questo ha il vantaggio di enumerare la sequenza list solo una volta. Anche se potrebbe non importa se è un list List<T> che non cambia nel frattempo, potrebbe la materia per gli oggetti IEnumerable<T> arbitrari. Nulla garantisce che la sequenza non cambia in diverse enumerazioni così metodi che stanno facendo più volte può essere pericoloso (e inefficiente, a seconda della natura della sequenza). Tuttavia, è ancora un meno di una soluzione ideale per le grandi sequenze. Suggerisco di scrivere proprio interno MaxObject manualmente se si dispone di un grande insieme di elementi da essere in grado di farlo in un solo passaggio, senza l'ordinamento e altre cose di sorta (O (n)):

static class EnumerableExtensions {
    public static T MaxObject<T,U>(this IEnumerable<T> source, Func<T,U> selector)
      where U : IComparable<U> {
       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 utilizzarlo con:

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

Fare un ordinamento e quindi selezionando la prima voce sta sprecando un sacco di tempo per ordinare gli articoli dopo il primo. Non ti importa circa l'ordine di quelli.

Invece è possibile 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 provare con questo ??? :

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

o più optimize:

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. Pianura, conciso LINQ;
  2. O (n) la complessità;
  3. Non valutare la proprietà più di una volta per ogni elemento.

Qui è:

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

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

Utilizzo:

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

Credo che l'ordinamento per la colonna che si desidera ottenere il massimo di e poi afferrare il primo dovrebbe funzionare. Tuttavia, se sono presenti più oggetti con lo stesso valore MAX, verrà afferrato sola:

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<test> arr = new List<test>();
    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) si potrebbe fare come segue:

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

che genererà SQL come segue:

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')

Il che sembra abbastanza efficiente per me.

In base prima risposta di Cameron, ecco quello che ho appena aggiunto alla mia versione migliorata del FloatingWindowHost di SilverFlow biblioteca (copia da FloatingWindowHost.cs a http://clipflair.codeplex.com codice sorgente)

    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);
    }

Da notare relativo codice precedente che Canvas.ZIndex è una proprietà associata disponibili per UIElements in vari contenitori, non appena utilizzato quando viene ospitato in una tela (vedi controllo dell'ordine di rendering (ZOrder) in Silverlight senza utilizzare il controllo tela ). Immagino si potrebbe anche fare un SetTopmost e metodo di estensione statici SetBottomMost per UIElement facilmente adattando questo codice.

È inoltre possibile aggiornare la soluzione di Mehrdad Afshari riscrivendo il metodo estensione a più veloce (e più bello) una:

static class EnumerableExtensions
{
    public static T MaxElement<T, R>(this IEnumerable<T> container, Func<T, R> 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);

Questo nome sarà chiaro a persone che utilizzano C ++ (perché non v'è std :: max_element in là).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top