Le moyen le plus propre d'obtenir le dernier élément de l'itérateur Python

110

Quelle est la meilleure façon d'obtenir le dernier élément d'un itérateur dans Python 2.6? Par exemple, dites

my_iter = iter(range(5))

Quel est le plus court / Code manière la plus propre d'obtenir à 4partir my_iter?

Je pourrais le faire, mais cela ne semble pas très efficace:

[x for x in my_iter][-1]
Peter
la source
4
Les itérateurs supposent que vous voulez parcourir les éléments et ne pas vraiment accéder aux derniers éléments. Qu'est-ce qui vous empêche d'utiliser simplement range (5) [- 1]?
Frank
7
@Frank - J'ai supposé que l'itérateur réel était plus complexe et / ou plus éloigné et / ou plus difficile à contrôler queiter(range(5))
Chris Lutz
3
@Frank: le fait que c'est en fait une fonction de générateur beaucoup plus compliquée qui fournit l'itérateur. J'ai juste inventé cet exemple pour que ce qui se passait soit simple et clair.
Peter le
4
Si vous voulez le dernier élément d'un itérateur, il y a de fortes chances que vous fassiez quelque chose de mal. Mais la réponse est qu'il n'y a pas vraiment de moyen plus propre d'itérer à travers l'itérateur. En effet, les itérateurs n'ont pas de taille et, en fait, peuvent ne jamais se terminer du tout et, en tant que tels, peuvent ne pas avoir de dernier élément. (Cela signifie que votre code fonctionnera pour toujours, bien sûr). La question persistante est donc: pourquoi voulez-vous le dernier élément d'un itérateur?
Lennart Regebro
3
@Peter: Mettez à jour votre question, s'il vous plaît. N'ajoutez pas beaucoup de commentaires à une question qui vous appartient. Veuillez mettre à jour la question et supprimer les commentaires.
S.Lott

Réponses:

100
item = defaultvalue
for item in my_iter:
    pass
Thomas Wouters
la source
4
Pourquoi l'espace réservé «defaultvalue»? Pourquoi pas None? C'est précisément à cela que Nonesert. Êtes-vous en train de suggérer qu'une valeur par défaut spécifique à une fonction pourrait même être correcte? Si l'itérateur n'itère pas réellement, alors une valeur hors bande est plus significative qu'une valeur par défaut spécifique à une fonction trompeuse.
S.Lott
46
La valeur par défaut est juste un espace réservé pour mon exemple. Si vous souhaitez utiliser Nonecomme valeur par défaut, c'est votre choix. Aucun n'est pas toujours la valeur par défaut la plus sensée, et peut même ne pas être hors bande. Personnellement, j'ai tendance à utiliser 'defaultvalue = object ()' pour m'assurer que c'est une valeur vraiment unique. J'indique simplement que le choix de la valeur par défaut est en dehors de la portée de cet exemple.
Thomas Wouters
28
@ S.Lott: il est peut-être utile de distinguer la différence entre un itérateur vide et un itérateur qui a Nonepour valeur finale
John La Rooy
8
Il y a une erreur de conception dans tous les itérateurs de tous les types de conteneurs intégrés? Première fois que j'en ai entendu parler :)
Thomas Wouters
7
Bien que ce soit probablement la solution la plus rapide, elle repose sur la variable qui fuit dans les boucles for (une fonctionnalité pour certains, un bogue pour d'autres - probablement les FP-guys sont consternés). Quoi qu'il en soit, Guido a déclaré que cela fonctionnera toujours de cette façon, donc c'est une construction sûre à utiliser.
tokland
68

Utilisez un dequede taille 1.

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()
martin23487234
la source
6
C'est en fait le moyen le plus rapide d'épuiser une longue séquence, bien que légèrement plus rapide que la boucle for.
Sven Marnach
11
+1 pour être techniquement correct, mais les lecteurs devraient avoir les mises en garde habituelles de Python: "Avez-vous VRAIMENT besoin d'optimiser cela?", "C'est moins explicite, ce qui n'est pas Pythonic", et "La vitesse plus rapide dépend de l'implémentation, qui peut changer."
leewz
1
aussi, c'est un souvenir
Eelco Hoogendoorn
6
@EelcoHoogendoorn Pourquoi est-ce un bourreau de mémoire, même avec un maxlen de 1?
Chris Wesseling
1
De toutes les solutions présentées ici jusqu'à présent, je trouve que c'est la plus rapide et la plus efficace en mémoire .
Markus Strauss
66

Si vous utilisez Python 3.x:

*_, last = iterator # for a better understanding check PEP 448
print(last)

si vous utilisez python 2.7:

last = next(iterator)
for last in iterator:
    continue
print last


Note latérale:

Habituellement, la solution présentée ci-dessus est celle dont vous avez besoin pour les cas réguliers, mais si vous avez affaire à une grande quantité de données, il est plus efficace d'utiliser un dequede taille 1. ( source )

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()
DhiaTN
la source
1
@virtualxtc: Le trait de soulignement n'est qu'un identifiant. L'étoile en face dit "élargir la liste". Le plus lisible serait *lst, last = some_iterable.
pepr le
4
@virtualxtc nope _est une variable spéciale en python et utilisée soit pour stocker la dernière valeur, soit pour dire que je ne me soucie pas de la valeur et qu'elle peut donc être nettoyée.
DhiaTN
1
Cette solution Python 3 n'est pas efficace en mémoire.
Markus Strauss
3
@DhiaTN Oui, vous avez absolument raison. En fait, j'aime beaucoup l'idiome Python 3 que vous avez montré. Je voulais juste préciser que cela ne fonctionne pas pour le «big data». J'utilise collections.deque pour cela, qui se trouve être rapide et efficace en mémoire (voir la solution de martin23487234).
Markus Strauss
1
Cet exemple py3.5 + devrait être dans PEP 448. Merveilleux.
EliadL
33

__reversed__Cela vaut probablement la peine d'être utilisé s'il est disponible

if hasattr(my_iter,'__reversed__'):
    last = next(reversed(my_iter))
else:
    for last in my_iter:
        pass
John La Rooy
la source
27

Aussi simple que:

max(enumerate(the_iter))[1]
Chema Cortes
la source
8
Oh, c'est intelligent. Pas le plus efficace ni le plus lisible, mais intelligent.
timgeb
6
Donc, penser à voix haute ... Cela fonctionne parce que enumerateretourne (index, value)comme: (0, val0), (1, val1), (2, val2)... et ensuite, par défaut, maxlorsqu'on lui donne une liste de tuples, se compare uniquement à la première valeur du tuple, à moins que deux premières valeurs ne soient égales, ce qu'elles ne sont jamais ici car ils représentent des indices. Ensuite, l'indice de fin est que max renvoie le tuple entier (idx, valeur) alors que nous ne sommes intéressés que par value. Idée intéressante.
Taylor Edmiston
21

Il est peu probable que ce soit plus rapide que la boucle for vide à cause du lambda, mais cela donnera peut-être une idée à quelqu'un d'autre

reduce(lambda x,y:y,my_iter)

Si l'iter est vide, une TypeError est levée

John La Rooy
la source
À mon humble avis, celui-ci est le plus direct, conceptuellement. Au lieu d'augmenter TypeErrorpour un itérables vide, vous pouvez également fournir une valeur par défaut par la valeur initiale reduce(), par exemple last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default).
egnha
9

Il y a ça

list( the_iter )[-1]

Si la durée de l'itération est vraiment épique - si longue que la matérialisation de la liste épuisera la mémoire - alors vous devez vraiment repenser la conception.

S.Lott
la source
1
C'est la solution la plus simple.
laike9m
2
Il est légèrement préférable d'utiliser un tuple.
Christopher Smith
9
Pas du tout d'accord avec la dernière phrase. Travailler avec des ensembles de données très volumineux (qui pourraient dépasser les limites de la mémoire s'ils sont chargés en une seule fois) est la principale raison d'utiliser un itérateur au lieu d'une liste.
Paul
@Paul: certaines fonctions ne renvoient qu'un itérateur. C'est un moyen court et assez lisible de le faire dans ce cas (pour les listes non épiques).
serv-inc
C'est la manière la moins efficace d'éviter une mauvaise mauvaise habitude. Une autre consiste à utiliser sort (sequence) [- 1] pour obtenir l'élément max de la séquence. Veuillez ne jamais utiliser ces mauvais modèles si vous aimez être ingénieur logiciel.
Maksym Ganenko
5

J'utiliserais reversed, sauf qu'il ne prend que des séquences au lieu d'itérateurs, ce qui semble plutôt arbitraire.

Quoi qu'il en soit, vous devrez parcourir tout l'itérateur. Avec une efficacité maximale, si vous n'avez plus jamais besoin de l'itérateur, vous pouvez simplement supprimer toutes les valeurs:

for last in my_iter:
    pass
# last is now the last item

Je pense cependant que c'est une solution sous-optimale.

Chris Lutz
la source
4
reverse () ne prend pas d'itérateur, juste des séquences.
Thomas Wouters
3
Ce n'est pas du tout arbitraire. La seule façon d'inverser un itérateur est d'itérer jusqu'à la fin, tout en gardant tous les éléments en mémoire. Je, e, vous devez d'abord en faire une séquence avant de pouvoir l'inverser. Ce qui, bien sûr, va à l'encontre de l'objectif de l'itérateur en premier lieu, et signifierait également que vous utilisez soudainement beaucoup de mémoire sans raison apparente. C'est donc le contraire de l'arbitraire, en fait. :)
Lennart Regebro
@Lennart - Quand j'ai dit arbitraire, je voulais dire ennuyeux. Je concentre mes compétences linguistiques sur mon papier dans quelques heures à cette heure du matin.
Chris Lutz
3
C'est suffisant. Bien que l'OMI, ce serait plus ennuyeux s'il acceptait les itérateurs, car presque toute utilisation de celui-ci serait une mauvaise idée (tm). :)
Lennart Regebro
3

La bibliothèque toolz fournit une solution intéressante:

from toolz.itertoolz import last
last(values)

Mais l'ajout d'une dépendance non essentielle peut ne pas valoir la peine de l'utiliser uniquement dans ce cas.

lombaire
la source
0

Je voudrais juste utiliser next(reversed(myiter))

thomas.mac
la source
8
TypeError: l'argument de reverse () doit être une séquence
Labo
0

La question est d'obtenir le dernier élément d'un itérateur, mais si votre itérateur est créé en appliquant des conditions à une séquence, alors inversé peut être utilisé pour trouver le "premier" d'une séquence inversée, en regardant uniquement les éléments nécessaires, en appliquant inverser la séquence elle-même.

Un exemple artificiel,

>>> seq = list(range(10))
>>> last_even = next(_ for _ in reversed(seq) if _ % 2 == 0)
>>> last_even
8
Wyrmwood
la source
0

Alternativement, pour les itérateurs infinis, vous pouvez utiliser:

from itertools import islice 
last = list(islice(iterator(), 1000))[-1] # where 1000 is number of samples 

Je pensais que ce serait plus lent alors dequemais c'est aussi rapide et c'est en fait plus rapide que pour la méthode en boucle (en quelque sorte)

qocu
la source
-6

La question est fausse et ne peut conduire qu'à une réponse compliquée et inefficace. Pour obtenir un itérateur, vous partez bien sûr de quelque chose qui est itérable, qui dans la plupart des cas offrira un moyen plus direct d'accéder au dernier élément.

Une fois que vous avez créé un itérateur à partir d'un itérable, vous êtes obligé de parcourir les éléments, car c'est la seule chose qu'un itérable fournit.

Ainsi, le moyen le plus efficace et le plus clair n'est pas de créer l'itérateur en premier lieu mais d'utiliser les méthodes d'accès natives de l'itérable.

Ludwig
la source
5
Alors, comment obtiendriez-vous la dernière ligne d'un fichier?
Brice M. Dempsey
@ BriceM.Dempsey La meilleure façon n'est pas de parcourir le fichier entier (peut-être énorme) mais en allant à la taille du fichier moins 100, lisez les 100 derniers octets, recherchez une nouvelle ligne, s'il n'y en a pas, allez en arrière de 100 octets supplémentaires, etc. Vous pouvez également augmenter la taille de votre pas en arrière, en fonction de votre scénario. La lecture d'un milliard de lignes n'est certainement pas une solution optimale.
Alfe