Étant donné une collection, existe-t-il un moyen d'obtenir les N derniers éléments de cette collection? S'il n'y a pas de méthode dans le framework, quelle serait la meilleure façon d'écrire une méthode d'extension pour ce faire?
collection.Skip(Math.Max(0, collection.Count() - N));
Cette approche préserve l'ordre des éléments sans dépendre d'aucun tri et a une large compatibilité entre plusieurs fournisseurs LINQ.
Il est important de ne pas appeler Skip
avec un numéro négatif. Certains fournisseurs, tels que Entity Framework, produisent une ArgumentException lorsqu'ils sont présentés avec un argument négatif. L'appel à Math.Max
évite cela proprement.
La classe ci-dessous contient tous les éléments essentiels des méthodes d'extension, qui sont: une classe statique, une méthode statique et l'utilisation du this
mot clé.
public static class MiscExtensions
{
// Ex: collection.TakeLast(5);
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N)
{
return source.Skip(Math.Max(0, source.Count() - N));
}
}
Une brève note sur les performances:
Étant donné que l'appel à Count()
peut provoquer l'énumération de certaines structures de données, cette approche présente le risque de provoquer deux passages sur les données. Ce n'est pas vraiment un problème avec la plupart des énumérables; en fait, des optimisations existent déjà pour les requêtes Lists, Arrays et même EF pour évaluer l' Count()
opération en temps O (1).
Si, toutefois, vous devez utiliser un énumérable uniquement vers l'avant et que vous souhaitez éviter d'effectuer deux passes, envisagez un algorithme à une passe comme Lasse V. Karlsen ou Mark Byers le décrivent. Ces deux approches utilisent un tampon temporaire pour conserver les éléments lors de l'énumération, qui sont générés une fois la fin de la collection trouvée.
List
s etLinkedList
s, la solution de James a tendance à être plus rapide, mais pas d'un ordre de grandeur. Si l'IEnumerable est calculé (via Enumerable.Range, par exemple), la solution de James prend plus de temps. Je ne peux penser à aucun moyen de garantir un seul passage sans savoir quelque chose sur l'implémentation ou copier des valeurs dans une structure de données différente.MISE À JOUR: Pour résoudre le problème de clintp: a) L'utilisation de la méthode TakeLast () que j'ai définie ci-dessus résout le problème, mais si vous voulez vraiment le faire sans la méthode supplémentaire, alors il vous suffit de reconnaître que si Enumerable.Reverse () peut être utilisé comme méthode d'extension, vous n'êtes pas obligé de l'utiliser de cette façon:
la source
List<string> mystring = new List<string>() { "one", "two", "three" }; mystring = mystring.Reverse().Take(2).Reverse();
j'obtiens une erreur de compilation car .Reverse () retourne void et le compilateur choisit cette méthode à la place de Linq qui retourne un IEnumerable. Suggestions?N
enregistrements, vous pouvez ignorer le secondReverse
.Remarque : J'ai raté le titre de votre question qui disait Utilisation de Linq , donc ma réponse n'utilise pas en fait Linq.
Si vous voulez éviter de mettre en cache une copie non paresseuse de la collection entière, vous pouvez écrire une méthode simple qui le fait en utilisant une liste liée.
La méthode suivante ajoute chaque valeur trouvée dans la collection d'origine dans une liste liée et réduit la liste liée au nombre d'éléments requis. Puisqu'il conserve la liste chaînée ajustée à ce nombre d'éléments tout au long de l'itération de la collection, il ne conserve qu'une copie d'au plus N éléments de la collection d'origine.
Il ne vous oblige pas à connaître le nombre d'articles dans la collection d'origine, ni à le parcourir plus d'une fois.
Usage:
Méthode d'extension:
la source
Voici une méthode qui fonctionne sur n'importe quel énumérable mais utilise uniquement le stockage temporaire O (N):
Usage:
Il fonctionne en utilisant un tampon circulaire de taille N pour stocker les éléments tels qu'ils les voient, en remplaçant les anciens éléments par de nouveaux. Lorsque la fin de l'énumérable est atteinte, le tampon en anneau contient les N derniers éléments.
la source
n
..NET Core 2.0+ fournit la méthode LINQ
TakeLast()
:https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takelast
exemple :
la source
netcoreapp1.x
) mais uniquement pour les versions 2.0 et 2.1 de dotnetcore (netcoreapp2.x
). Il est possible que vous cibliez le framework complet (par exemplenet472
) qui n'est également pas pris en charge. (Les bibliothèques standard .net peuvent être utilisées par n'importe lequel des éléments ci-dessus mais ne peuvent exposer que certaines API spécifiques à un framework cible. voir docs.microsoft.com/en-us/dotnet/standard/frameworks )Je suis surpris que personne ne l'ait mentionné, mais SkipWhile a une méthode qui utilise l'index de l'élément .
Le seul avantage perceptible que cette solution présente par rapport aux autres est que vous pouvez avoir la possibilité d'ajouter un prédicat pour créer une requête LINQ plus puissante et efficace, au lieu d'avoir deux opérations distinctes qui traversent deux fois l'IEnumerable.
la source
Utilisez EnumerableEx.TakeLast dans l'assembly System.Interactive de RX. C'est une implémentation O (N) comme @ Mark's, mais elle utilise une file d'attente plutôt qu'une construction de tampon en anneau (et supprime les éléments lorsqu'elle atteint la capacité de la mémoire tampon).
(NB: il s'agit de la version IEnumerable - pas de la version IObservable, bien que l'implémentation des deux soit à peu près identique)
la source
Queue<T>
implémenté à l'aide d'un tampon circulaire ?Si vous traitez une collection avec une clé (par exemple des entrées d'une base de données), une solution rapide (c'est-à-dire plus rapide que la réponse sélectionnée) serait
la source
Si cela ne vous dérange pas de plonger dans Rx dans le cadre de la monade, vous pouvez utiliser
TakeLast
:la source
Si l'utilisation d'une bibliothèque tierce est une option, MoreLinq définit
TakeLast()
qui fait exactement cela.la source
J'ai essayé de combiner efficacité et simplicité et je me suis retrouvé avec ceci:
À propos des performances: en C #,
Queue<T>
est implémenté à l'aide d'un tampon circulaire de sorte qu'aucune instanciation d'objet n'est effectuée à chaque boucle (uniquement lorsque la file d'attente augmente). Je n'ai pas défini la capacité de la file d'attente (en utilisant un constructeur dédié) car quelqu'un pourrait appeler cette extension aveccount = int.MaxValue
. Pour des performances supplémentaires, vous pouvez vérifier si l'implémentation sourceIList<T>
et si oui, extraire directement les dernières valeurs à l'aide des index de tableau.la source
Il est un peu inefficace de prendre le dernier N d'une collection à l'aide de LINQ car toutes les solutions ci-dessus nécessitent une itération à travers la collection.
TakeLast(int n)
dansSystem.Interactive
également ce problème.Si vous avez une liste, une chose plus efficace à faire est de la découper en utilisant la méthode suivante
avec
et certains cas de test
la source
Je sais qu'il est trop tard pour répondre à cette question. Mais si vous travaillez avec une collection de type IList <> et que vous ne vous souciez pas de l'ordre de la collection retournée, cette méthode fonctionne plus rapidement. J'ai utilisé la réponse de Mark Byers et j'ai apporté quelques modifications. Alors maintenant, la méthode TakeLast est:
Pour le test, j'ai utilisé la méthode Mark Byers et la réponse de kbrimington . C'est test:
Et voici les résultats pour prendre 10 éléments:
et pour prendre 1000001 éléments, les résultats sont:
la source
Voici ma solution:
Le code est un peu gros, mais en tant que composant réutilisable, il devrait fonctionner aussi bien que possible dans la plupart des scénarios, et il gardera le code qui l'utilise agréable et concis. :-)
Mon
TakeLast
pour nonIList`1
est basé sur le même algorithme de tampon en anneau que celui des réponses de @Mark Byers et @MackieChan plus haut. Il est intéressant de voir à quel point ils sont similaires - j'ai écrit le mien de manière totalement indépendante. Je suppose qu'il n'y a vraiment qu'une seule façon de faire un tampon en anneau correctement. :-)En regardant la réponse de @ kbrimington, une vérification supplémentaire pourrait être ajoutée à cela pour
IQuerable<T>
revenir à l'approche qui fonctionne bien avec Entity Framework - en supposant que ce que j'ai à ce stade ne fonctionne pas.la source
Ci-dessous l'exemple réel comment prendre les 3 derniers éléments d'une collection (tableau):
la source
Utilisation de cette méthode pour obtenir toute la plage sans erreur
la source
Petite implémentation différente avec l'utilisation d'un tampon circulaire. Les benchmarks montrent que la méthode est environ deux fois plus rapide que celles utilisant Queue (implémentation de TakeLast dans System.Linq ), mais pas sans coût - elle a besoin d'un tampon qui croît avec le nombre d'éléments demandé, même si vous avez un petite collection, vous pouvez obtenir une énorme allocation de mémoire.
la source