J'ai une liste d'objets qui ont deux propriétés int. La liste est la sortie d'une autre requête linq. L'object:
public class DimensionPair
{
public int Height { get; set; }
public int Width { get; set; }
}
Je veux trouver et retourner l'objet dans la liste qui a la plus grande valeur de propriété Height
.
Je peux réussir à obtenir la valeur la plus élevée de la valeur Height
mais pas l'objet lui-même.
Puis-je faire cela avec Linq? Comment?
Nous avons un méthode d'extension pour faire exactement cela dans MoreLINQ . Vous pouvez regarder l’implémentation ici, mais fondamentalement, il s’agit de parcourir les données, en se rappelant l’élément maximum que nous avons vu jusqu’à présent et la valeur maximale qu’elle a produite sous la projection.
Dans votre cas, vous feriez quelque chose comme:
var item = items.MaxBy(x => x.Height);
Ceci est meilleur (IMO) que l’une des solutions présentées ici, autre que la deuxième solution de Mehrdad (qui est fondamentalement la même chose que MaxBy
):
Max
puis trouver le premier élément avec cette valeur est O (n), mais itère deux fois sur la séquence. Dans la mesure du possible, vous devriez utiliser LINQ en mode passe unique.Cela nécessiterait un tri (O (n log n)) mais est très simple et flexible. Un autre avantage est de pouvoir l'utiliser avec LINQ to SQL:
var maxObject = list.OrderByDescending(item => item.Height).First();
Notez que cela a l’avantage d’énumérer la séquence list
une seule fois. Bien que cela puisse avoir peu d'importance si list
est un List<T>
qui ne change pas entre-temps, cela pourrait être important pour les objets IEnumerable<T>
arbitraires. Rien ne garantit que la séquence ne change pas dans différentes énumérations, aussi les méthodes qui le font plusieurs fois peuvent être dangereuses (et inefficaces, en fonction de la nature de la séquence). Cependant, cela reste une solution moins qu'idéale pour les grandes séquences. Je suggère d’écrire manuellement votre propre extension MaxObject
si vous disposez d’un grand nombre d’articles pour pouvoir le faire en un seul passage, sans trier ni effectuer d’autres tâches (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;
}
}
et l'utiliser avec:
var maxObject = list.MaxObject(item => item.Height);
Effectuer une commande puis sélectionner le premier article vous fait perdre beaucoup de temps à les commander après le premier. Vous ne vous souciez pas de l'ordre de ceux-ci.
À la place, vous pouvez utiliser la fonction d'agrégation pour sélectionner le meilleur élément en fonction de vos besoins.
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);
Et pourquoi n'essayes-tu pas avec ça ??? :
var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height));
OU plus optimiser:
var itemMaxHeight = items.Max(y => y.Height);
var itemsMax = items.Where(x => x.Height == itemMaxHeight);
mmm?
Les réponses à ce jour sont super! Mais je vois un besoin de solution avec les contraintes suivantes:
C'est ici:
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;
}
Usage:
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
Je pense que le tri en fonction de la colonne dont vous souhaitez obtenir le MAX et la saisie de la première devraient fonctionner. Cependant, s'il y a plusieurs objets avec la même valeur MAX, un seul sera saisi:
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; }
}
Dans NHibernate (avec NHibernate.Linq), vous pouvez le faire comme suit:
return session.Query<T>()
.Single(a => a.Filter == filter &&
a.Id == session.Query<T>()
.Where(a2 => a2.Filter == filter)
.Max(a2 => a2.Id));
Ce qui va générer du SQL comme suit:
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')
Ce qui me semble plutôt efficace.
Vous pouvez également mettre à niveau la solution de Mehrdad Afshari en réécrivant la méthode d'extention en une méthode plus rapide (et plus esthétique):
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;
}
}
Et puis juste l'utiliser:
var maxObject = list.MaxElement(item => item.Height);
Ce nom sera clair pour les personnes utilisant C++ (car std :: max_element est présent).
D'après la réponse initiale de Cameron, voici ce que je viens d'ajouter à ma version améliorée de FloatingWindowHost de la bibliothèque SilverFlow (copie de FloatingWindowHost.cs à l'adresse http://clipflair.codeplex.com code source)
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);
}
Il est à noter que, dans le code ci-dessus, Canvas.ZIndex est une propriété attachée disponible pour les droits UIE dans divers conteneurs, et non uniquement utilisée lorsqu'elle est hébergée dans un canevas (voir Contrôle de l'ordre de rendu (ZOrder) dans Silverlight sans utiliser le contrôle Canvas ). On pourrait même faire une méthode d’extension statique SetTopmost et SetBottomMost pour UIElement facilement en adaptant ce code.