Quel est le moyen pythonique de détecter le dernier élément d'une boucle 'pour' en python?

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

Question

J'aimerais connaître le meilleur moyen (plus compact et "pythonique") de faire un traitement spécial pour le dernier élément d'une boucle for. Il y a un morceau de code qui devrait être appelé uniquement entre éléments, étant supprimé dans le dernier.

Voici comment je le fais actuellement:

for i, data in enumerate(data_list):
    code_that_is_done_for_every_element
    if i != len(data_list) - 1:
        code_that_is_done_between_elements

Y a-t-il une meilleure solution?

Remarque: je ne veux pas le faire avec des hacks tels que l’utilisation de réduire ;)

Était-ce utile?

La solution

La plupart du temps, il est plus facile (et moins coûteux) de faire de la première itération le cas spécial au lieu de la dernière:

first = True
for data in data_list:
    if first:
        first = False
    else:
        between_items()

    item()

Ceci fonctionnera pour tous les itératifs, même pour ceux qui n'ont pas len () :

file = open('/path/to/file')
for line in file:
    process_line(line)

    # No way of telling if this is the last line!

À part cela, je ne pense pas qu'il existe une solution généralement supérieure, car cela dépend de ce que vous essayez de faire. Par exemple, si vous créez une chaîne à partir d'une liste, il est naturellement préférable d'utiliser str.join () que d'utiliser un code pour en boucle & # 8220; avec un cas particulier & # 8221;.

En utilisant le même principe mais plus compact:

for i, line in enumerate(data_list):
    if i > 0:
        between_items()
    item()

semble familier, n'est-ce pas? :)

Pour @ofko et les autres qui ont vraiment besoin de savoir si la valeur actuelle d'un itérable sans len () est la dernière, vous devez regarder plus loin:

def lookahead(iterable):
    """Pass through all values from the given iterable, augmented by the
    information if there are more values to come after the current one
    (True), or if it is the last value (False).
    """
    # Get an iterator and pull the first value.
    it = iter(iterable)
    last = next(it)
    # Run the iterator to exhaustion (starting from the second value).
    for val in it:
        # Report the *previous* value (more to come).
        yield last, True
        last = val
    # Report the last value.
    yield last, False

Ensuite, vous pouvez l'utiliser comme ceci:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False

Autres conseils

Le "code entre" est un exemple du modèle Head-Tail .

Vous avez un élément suivi d'une séquence de paires (entre éléments). Vous pouvez également voir cela comme une séquence de paires (élément, entre) suivies d'un élément. Il est généralement plus simple de prendre le premier élément comme spécial et tous les autres comme le paramètre "standard". cas.

De plus, pour éviter de répéter le code, vous devez fournir une fonction ou un autre objet contenant le code que vous ne souhaitez pas répéter. Incorporer une instruction if dans une boucle qui est toujours fausse, sauf qu'une fois est un peu ridicule.

def item_processing( item ):
    # *the common processing*

head_tail_iter = iter( someSequence )
head = head_tail_iter.next()
item_processing( head )
for item in head_tail_iter:
    # *the between processing*
    item_processing( item )

C’est plus fiable car c’est un peu plus facile à prouver, cela ne crée pas de structure de données supplémentaire (c’est-à-dire une copie d’une liste) et ne nécessite pas beaucoup d’exécution gaspillée d’un si condition qui est toujours fausse sauf une fois.

Si vous souhaitez simplement modifier le dernier élément de data_list , vous pouvez simplement utiliser la notation suivante:

L[-1]

Cependant, il semble que vous fassiez plus que cela. Il n'y a rien de mal à votre façon. J'ai même jeté un coup d'œil rapide sur un code Django pour leurs balises de modèle et ils font essentiellement ce que vous faites.

Bien que cette question soit assez ancienne, je suis arrivée ici via Google et j’ai trouvé un moyen assez simple: découper des listes. Disons que vous voulez mettre un '& amp;' entre toutes les entrées de la liste.

s = ""
l = [1, 2, 3]
for i in l[:-1]:
    s = s + str(i) + ' & '
s = s + str(l[-1])

Ceci renvoie '1 & amp; 2 & amp; 3 '.

Ceci est similaire à l'approche de Ants Aasma mais sans utiliser le module itertools. C'est aussi un itérateur en retard qui regarde en avant un seul élément du flux d'itérateurs:

def last_iter(it):
    # Ensure it's an iterator and get the first field
    it = iter(it)
    prev = next(it)
    for item in it:
        # Lag by one item so I know I'm not at the end
        yield 0, prev
        prev = item
    # Last item
    yield 1, prev

def test(data):
    result = list(last_iter(data))
    if not result:
        return
    if len(result) > 1:
        assert set(x[0] for x in result[:-1]) == set([0]), result
    assert result[-1][0] == 1

test([])
test([1])
test([1, 2])
test(range(5))
test(xrange(4))

for is_last, item in last_iter("Hi!"):
    print is_last, item

si les éléments sont uniques:

for x in list:
    #code
    if x == list[-1]:
        #code

autres options:

pos = -1
for x in list:
    pos += 1
    #code
    if pos == len(list) - 1:
        #code


for x in list:
    #code
#code - e.g. print x


if len(list) > 0:
    for x in list[:-1]
        #code
    for x in list[-1]:
        #code

Vous pouvez utiliser une fenêtre glissante sur les données d’entrée pour avoir un aperçu de la valeur suivante et utiliser une sentinelle pour détecter la dernière valeur. Cela fonctionne sur toutes les valeurs, donc vous n'avez pas besoin de connaître la longueur à l'avance. L’implémentation par paire provient des recettes d'itertools .

from itertools import tee, izip, chain

def pairwise(seq):
    a,b = tee(seq)
    next(b, None)
    return izip(a,b)

def annotated_last(seq):
    """Returns an iterable of pairs of input item and a boolean that show if
    the current item is the last item in the sequence."""
    MISSING = object()
    for current_item, next_item in pairwise(chain(seq, [MISSING])):
        yield current_item, next_item is MISSING:

for item, is_last_item in annotated_last(data_list):
    if is_last_item:
        # current item is the last item

N'y a-t-il aucune possibilité d'itérer tout sauf le dernier élément et de traiter le dernier en dehors de la boucle? Après tout, une boucle est créée pour faire quelque chose de similaire à tous les éléments survolés; si un élément a besoin de quelque chose de spécial, il ne devrait pas être dans la boucle.

(Voir aussi cette question: ne -le-dernier-élément-dans-une-boucle-mérite-un-traitement-séparé )

EDIT: étant donné que la question porte davantage sur l'élément "entre les", l'élément premier est l'élément spécial en ce qu'il n'a pas de prédécesseur ou l'élément dernier Cet élément a ceci de particulier qu'il n'a pas de successeur.

Il n’ya rien de mal avec votre chemin, à moins que vous n’ayez 100 000 boucles et que vous souhaitiez économiser 100 000 "si " déclarations. Dans ce cas, vous pouvez suivre cette voie:

iterable = [1,2,3] # Your date
iterator = iter(iterable) # get the data iterator

try :   # wrap all in a try / except
    while 1 : 
        item = iterator.next() 
        print item # put the "for loop" code here
except StopIteration, e : # make the process on the last element here
    print item

Sorties:

1
2
3
3

Mais vraiment, dans votre cas, j'ai l'impression que c'est exagéré.

Dans tous les cas, vous aurez probablement plus de chance avec le découpage en tranches:

for item in iterable[:-1] :
    print item
print "last :", iterable[-1]

#outputs
1
2
last : 3

ou juste:

for item in iterable :
    print item
print iterable[-1]

#outputs
1
2
3
last : 3

Éventuellement, une façon KISS de faire votre travail, et cela fonctionnerait avec tous les paramètres, y compris ceux sans __ len __ :

item = ''
for item in iterable :
    print item
print item

Ouputs:

<*>

Si je sens que je le ferais de cette façon, cela me semble simple.

Utilisez le découpage en tranches et est pour rechercher le dernier élément:

for data in data_list:
    <code_that_is_done_for_every_element>
    if not data is data_list[-1]:
        <code_that_is_done_between_elements>

Caveat emptor : cela ne fonctionne que si tous les éléments de la liste sont réellement différents (ont des emplacements différents en mémoire). Sous le capot, Python peut détecter des éléments égaux et réutiliser les mêmes objets pour eux. Par exemple, pour les chaînes de même valeur et les entiers communs.

Google m'a amené à cette vieille question et je pense que je pourrais ajouter une approche différente à ce problème.

La plupart des réponses ici traitent du traitement approprié d'un contrôle de boucle for tel qu'il a été demandé, mais si la liste de données est destructible, je vous suggérerais de supprimer les éléments de la liste jusqu'à ce que vous obteniez une liste vide. :

while True:
    element = element_list.pop(0)
    do_this_for_all_elements()
    if not element:
        do_this_only_for_last_element()
        break
    do_this_for_all_elements_but_last()

vous pouvez même utiliser while len (liste_éléments) si vous n'avez rien à faire avec le dernier élément. Je trouve cette solution plus élégante que de traiter avec next ().

J'aime l'approche de @ ethan-t, mais tant que True est dangereux de mon point de vue.

while L:
    e = L.pop(0)
    # process element
    if not L:
        print('Last element has been detected.')

Reportez le traitement spécial du dernier élément à la fin de la boucle.

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3

En supposant que l'entrée soit un itérateur, voici une manière d'utiliser tee et izip depuis itertools:

from itertools import tee, izip
items, between = tee(input_iterator, 2)  # Input must be an iterator.
first = items.next()
do_to_every_item(first)  # All "do to every" operations done to first item go here.
for i, b in izip(items, between):
    do_between_items(b)  # All "between" operations go here.
    do_to_every_item(i)  # All "do to every" operations go here.

Démo:

>>> def do_every(x): print "E", x
...
>>> def do_between(x): print "B", x
...
>>> test_input = iter(range(5))
>>>
>>> from itertools import tee, izip
>>>
>>> items, between = tee(test_input, 2)
>>> first = items.next()
>>> do_every(first)
E 0
>>> for i,b in izip(items, between):
...     do_between(b)
...     do_every(i)
...
B 0
E 1
B 1
E 2
B 2
E 3
B 3
E 4
>>>

si vous parcourez la liste, cela a également fonctionné pour moi:

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()

La solution la plus simple qui me vienne à l’esprit est la suivante:

for item in data_list:
    try:
        print(new)
    except NameError: pass
    new = item
print('The last item: ' + str(new))

Nous anticipons donc toujours un élément en retardant l’itération de traitement. Pour éviter de faire quelque chose lors de la première itération, j’attrape simplement l’erreur.

Bien sûr, vous devez réfléchir un peu, pour que NameError soit généré quand vous le souhaitez.

Conservez également le `compstruct

try:
    new
except NameError: pass
else:
    # continue here if no error was raised

Cela suppose que le nom nouveau n’a pas été défini auparavant. Si vous êtes paranoïaque, vous pouvez vous assurer que le nouveau n’existe pas avec:

try:
    del new
except NameError:
    pass

Vous pouvez également utiliser une instruction if ( si non pas d'abord: print (nouveau) sinon: notfirst = True ). Mais pour autant que je sache, les frais généraux sont plus importants.

Using `timeit` yields:

    ...: try: new = 'test' 
    ...: except NameError: pass
    ...: 
100000000 loops, best of 3: 16.2 ns per loop

Je m'attends donc à ce que le temps système ne soit pas sélectionnable.

Comptez les éléments une fois et suivez le nombre d'éléments restants:

remaining = len(data_list)
for data in data_list:
    code_that_is_done_for_every_element

    remaining -= 1
    if remaining:
        code_that_is_done_between_elements

Ainsi, vous n’évaluez qu’une fois la longueur de la liste. Beaucoup de solutions sur cette page semblent supposer que la longueur est indisponible à l'avance, mais cela ne fait pas partie de votre question. Si vous avez la longueur, utilisez-la.

Pour moi, le moyen le plus simple et pythonique de gérer un cas particulier à la fin d'une liste est le suivant:

for data in data_list[:-1]:
    handle_element(data)
handle_special_element(data_list[-1])

Bien sûr, cela peut également être utilisé pour traiter le premier élément de manière particulière.

Il peut y avoir plusieurs moyens. le tranchage sera le plus rapide. Ajout d'un autre utilisant la méthode .index ():

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top