J'ai un objet Person avec une propriété Nullable DateOfBirth. Existe-t-il un moyen d'utiliser LINQ pour interroger une liste d'objets Person pour celui avec la valeur DateOfBirth la plus ancienne / la plus petite.
Voici ce que j'ai commencé avec:
var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));
Les valeurs Null DateOfBirth sont définies sur DateTime.MaxValue afin de les exclure de la considération Min (en supposant qu'au moins une a un DOB spécifié).
Mais tout ce que cela fait pour moi, c'est de définir firstBornDate sur une valeur DateTime. Ce que j'aimerais obtenir, c'est l'objet Personne qui correspond à cela. Dois-je écrire une deuxième requête comme ceci:
var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);
Ou existe-t-il une façon plus simple de le faire?
a.Min(x => x.foo);
max("find a word of maximal length in this sentence".split(), key=len)
retourne la chaîne 'phrase'. En C #"find a word of maximal length in this sentence".Split().Max(word => word.Length)
calcule que 8 est la plus longue d'un mot, mais ne vous dit pas ce que le mot le plus long est .Réponses:
la source
curMin == null
?curMin
ne pourrait l'être quenull
si vous l'utilisiezAggregate()
avec une graine qui l'estnull
.Malheureusement, il n'y a pas de méthode intégrée pour le faire, mais c'est assez facile à mettre en œuvre par vous-même. En voici les tripes:
Exemple d'utilisation:
Notez que cela lèvera une exception si la séquence est vide et retournera le premier élément avec la valeur minimale s'il y en a plus d'un.
Alternativement, vous pouvez utiliser l'implémentation que nous avons dans MoreLINQ , dans MinBy.cs . (Il y a un correspondant
MaxBy
, bien sûr.)Installer via la console du gestionnaire de packages:
la source
REMARQUE: J'inclus cette réponse pour être complet car le PO n'a pas mentionné la source des données et nous ne devons faire aucune hypothèse.
Cette requête donne la bonne réponse, mais peut être plus lente car elle peut devoir trier tous les éléments
People
, selon la structure des donnéesPeople
:MISE À JOUR: En fait, je ne devrais pas appeler cette solution "naïve", mais l'utilisateur a besoin de savoir contre quoi il interroge. La «lenteur» de cette solution dépend des données sous-jacentes. S'il s'agit d'un tableau ou
List<T>
, alors LINQ to Objects n'a pas d'autre choix que de trier la collection entière avant de sélectionner le premier élément. Dans ce cas, il sera plus lent que l'autre solution suggérée. Toutefois, s'il s'agit d'une table LINQ to SQL et d'DateOfBirth
une colonne indexée, SQL Server utilisera l'index au lieu de trier toutes les lignes. D'autresIEnumerable<T>
implémentations personnalisées pourraient également utiliser des index (voir i4o: LINQ indexé ou la base de données d'objets db4o ) et rendre cette solution plus rapide queAggregate()
ouMaxBy()
/MinBy()
qui ont besoin d'itérer la collection entière une fois. En fait, LINQ to Objects aurait pu (en théorie) créer des cas spéciauxOrderBy()
pour des collections triées commeSortedList<T>
, mais ce n'est pas le cas, à ma connaissance.la source
Ferait l'affaire
la source
Vous demandez donc
ArgMin
ouArgMax
. C # n'a pas d'API intégrée pour ceux-ci.Je cherchais un moyen propre et efficace (O (n) dans le temps) de le faire. Et je pense que j'en ai trouvé un:
La forme générale de ce modèle est:
Surtout, en utilisant l'exemple de la question d'origine:
Pour C # 7.0 et supérieur qui prend en charge le tuple de valeur :
Pour la version C # antérieure à 7.0, le type anonyme peut être utilisé à la place:
Ils travaillent parce que les deux tuple de valeur et le type anonyme ont par défaut sensées comparateurs: pour (x1, y1) et (x2, y2), il compare d' abord
x1
vsx2
, puisy1
vsy2
. C'est pourquoi le intégré.Min
peut être utilisé sur ces types.Et comme le type anonyme et le tuple de valeur sont tous deux des types de valeur, ils devraient être tous deux très efficaces.
REMARQUE
Dans mes
ArgMin
implémentations ci-dessus, j'ai supposéDateOfBirth
prendre le typeDateTime
pour plus de simplicité et de clarté. La question d'origine demande d'exclure ces entrées avec unDateOfBirth
champ nul :Il peut être réalisé avec un pré-filtrage
C'est donc sans importance pour la question de la mise en œuvre de
ArgMin
ouArgMax
.NOTE 2
L'approche ci-dessus a une mise en garde: lorsque deux instances ont la même valeur minimale, l'
Min()
implémentation essaiera de comparer les instances en tant que bris d'égalité. Cependant, si la classe des instances n'est pas implémentéeIComparable
, une erreur d'exécution sera levée:Heureusement, cela peut encore être résolu de manière assez nette. L'idée est d'associer un "ID" distanct à chaque entrée qui sert de bris d'égalité sans ambiguïté. Nous pouvons utiliser un ID incrémentiel pour chaque entrée. En utilisant toujours l'âge des gens comme exemple:
la source
Solution sans packages supplémentaires:
vous pouvez également l'envelopper dans l'extension:
et dans ce cas:
Au fait ... O (n ^ 2) n'est pas la meilleure solution. Paul Betts a donné une solution plus grosse que la mienne. Mais ma solution est toujours LINQ et elle est plus simple et plus courte que les autres solutions ici.
la source
la source
Utilisation parfaitement simple de l'agrégat (équivalent à se plier dans d'autres langues):
Le seul inconvénient est que la propriété est accessible deux fois par élément de séquence, ce qui peut être coûteux. C'est difficile à résoudre.
la source
Voici la solution la plus générique. Il fait essentiellement la même chose (dans l'ordre O (N)) mais sur tous les types IEnumberable et peut être mélangé avec des types dont les sélecteurs de propriété pourraient retourner null.
Tests:
la source
MODIFIER à nouveau:
Désolé. En plus de manquer la valeur nulle, je regardais la mauvaise fonction,
Min <(Of <(TSource, TResult>)>) (IEnumerable <(Of <(TSource>)>), Func <(Of <(TSource, TResult>)>)) renvoie le type de résultat comme vous l'avez dit.
Je dirais qu'une solution possible est d'implémenter IComparable et d'utiliser Min <(Of <(TSource>)>) (IEnumerable <(Of <(TSource>)>)) , qui retourne vraiment un élément de IEnumerable. Bien sûr, cela ne vous aide pas si vous ne pouvez pas modifier l'élément. Je trouve le design de MS un peu bizarre ici.
Bien sûr, vous pouvez toujours faire une boucle for si vous en avez besoin, ou utiliser l'implémentation MoreLINQ de Jon Skeet.
la source
Une autre implémentation, qui pourrait fonctionner avec des clés de sélection nullables, et pour la collection de type de référence, renvoie null si aucun élément approprié n'a été trouvé. Cela pourrait être utile pour le traitement des résultats de la base de données par exemple.
Exemple:
la source
Essayez l'idée suivante:
la source
Je cherchais quelque chose de similaire moi-même, de préférence sans utiliser de bibliothèque ou trier la liste entière. Ma solution a fini par ressembler à la question elle-même, juste un peu simplifiée.
la source
var min = People.Min(...); var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == min...
Sinon, il obtient le min à plusieurs reprises jusqu'à ce qu'il trouve celui que vous recherchez.