Domanda

Mi piacerebbe conoscere il modo migliore (più compatto e "pythonic") per fare un trattamento speciale per l'ultimo elemento in un ciclo for. C'è un pezzo di codice che dovrebbe essere chiamato solo tra elementi, essere soppresso nell'ultimo.

Ecco come lo faccio attualmente:

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

C'è un modo migliore?

Nota: non voglio farcela con hack come usare riduci ;)

È stato utile?

Soluzione

Il più delle volte è più facile (ed economico) rendere la prima iterazione il caso speciale anziché l'ultimo:

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

    item()

Questo funzionerà per qualsiasi iterabile, anche per quelli che non hanno len () :

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

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

A parte questo, non penso che ci sia una soluzione generalmente superiore in quanto dipende da cosa stai cercando di fare. Ad esempio, se stai costruendo una stringa da un elenco, è naturalmente meglio usare str.join () piuttosto che usare un ciclo for & # 8220; con un caso speciale & # 8221;.


Utilizzando lo stesso principio ma più compatto:

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

Sembra familiare, vero? :)


Per @ofko e altri che hanno davvero bisogno di scoprire se il valore corrente di un iterabile senza len () è l'ultimo, dovrai guardare avanti:

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

Quindi puoi usarlo in questo modo:

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

Altri suggerimenti

Il 'codice tra' è un esempio del modello Head-Tail .

Hai un oggetto, seguito da una sequenza di (tra, oggetto) coppie. Puoi anche vederlo come una sequenza di coppie (oggetto, tra) seguite da un oggetto. È generalmente più semplice prendere il primo elemento come speciale e tutti gli altri come lo "standard" caso.

Inoltre, per evitare la ripetizione del codice, è necessario fornire una funzione o un altro oggetto per contenere il codice che non si desidera ripetere. Incorporare un'istruzione if in un ciclo che è sempre falso, tranne una volta è un po 'sciocco.

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 )

Questo è più affidabile perché è leggermente più facile da dimostrare, non crea una struttura di dati extra (cioè una copia di un elenco) e non richiede molta esecuzione sprecata di un se forte che è sempre falsa tranne una volta.

Se stai semplicemente cercando di modificare l'ultimo elemento in data_list , puoi semplicemente usare la notazione:

L[-1]

Tuttavia, sembra che tu stia facendo molto di più. Non c'è niente di veramente sbagliato nel tuo cammino. Ho anche dato una rapida occhiata ad alcuni codice Django per i loro tag modello e fanno praticamente quello che stai facendo.

Anche se questa domanda è piuttosto vecchia, sono venuta qui tramite Google e ho trovato un modo abbastanza semplice: tagliare la lista. Diciamo che vuoi mettere un '& amp;' tra tutte le voci dell'elenco.

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

Questo restituisce '1 & amp; 2 & amp; 3' .

Questo è simile all'approccio di Ants Aasma ma senza usare il modulo itertools. È anche un iteratore in ritardo che guarda avanti un singolo elemento nel flusso iteratore:

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

se gli articoli sono unici:

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

altre opzioni:

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

È possibile utilizzare una finestra scorrevole sopra i dati di input per dare un'occhiata al valore successivo e utilizzare una sentinella per rilevare l'ultimo valore. Funziona su qualsiasi iterabile, quindi non è necessario conoscere la lunghezza in anticipo. L'implementazione a coppie è da ricette 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

Non c'è possibilità di iterare su tutto tranne l'ultimo elemento e trattare l'ultimo al di fuori del ciclo? Dopotutto, viene creato un ciclo per fare qualcosa di simile a tutti gli elementi su cui giri; se un elemento ha bisogno di qualcosa di speciale, non dovrebbe essere nel ciclo.

(vedi anche questa domanda: -la-last-elemento-in-a-loop-meritano-a-parte-trattamento )

MODIFICA: poiché la domanda riguarda di più " tra " ;, o l'elemento primo è quello speciale in quanto non ha predecessore, oppure ultimo elemento è speciale in quanto non ha successori.

Non c'è niente di sbagliato nel tuo percorso, a meno che tu non abbia 100.000 loop e desideri risparmiare 100.000 "if" dichiarazioni. In tal caso, puoi procedere in questo modo:

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

Output:

1
2
3
3

Ma davvero, nel tuo caso, ho la sensazione che sia eccessivo.

In ogni caso, probabilmente sarai più fortunato con lo slicing:

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

#outputs
1
2
last : 3

o semplicemente:

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

#outputs
1
2
3
last : 3

Alla fine, un modo BACIO per fare cose, e che funzionerebbe con qualsiasi iterabile, compresi quelli senza __len__ :

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

ouputs:

<*>

Se ho voglia di farlo in questo modo, mi sembra semplice.

Usa slicing e è per verificare l'ultimo elemento:

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>

Avvertimento : funziona solo se tutti gli elementi nell'elenco sono effettivamente diversi (hanno posizioni diverse in memoria). Sotto il cofano, Python può rilevare elementi uguali e riutilizzare gli stessi oggetti per loro. Ad esempio, per stringhe dello stesso valore e numeri interi comuni.

Google mi ha portato a questa vecchia domanda e penso che potrei aggiungere un approccio diverso a questo problema.

La maggior parte delle risposte qui affronterebbe un trattamento adeguato di un controllo for loop come è stato chiesto, ma se la data_list è distruttibile, suggerirei di estrarre gli elementi dall'elenco fino a quando non si finisce con un elenco vuoto :

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()

potresti persino usare mentre len (element_list) se non hai bisogno di fare nulla con l'ultimo elemento. Trovo questa soluzione più elegante rispetto a next ().

Mi piace l'approccio di @ ethan-t, ma mentre True è pericoloso dal mio punto di vista.

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

Ritarda la gestione speciale dell'ultimo elemento fino a dopo il ciclo.

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

Supponendo l'input come iteratore, ecco un modo per usare tee e izip da 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.

Demo:

>>> 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
>>>

se stai esaminando l'elenco, anche per me ha funzionato:

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

La soluzione più semplice che mi viene in mente è:

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

Quindi guardiamo sempre avanti un elemento ritardando l'elaborazione di una iterazione. Per evitare di fare qualcosa durante la prima iterazione, rilevo semplicemente l'errore.

Ovviamente devi pensarci un po ', affinché NameError sia sollevato quando lo vuoi.

Conserva anche il `counstruct

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

Ciò si basa sul fatto che il nome nuovo non era stato precedentemente definito. Se sei paranoico puoi assicurarti che new non esista usando:

try:
    del new
except NameError:
    pass

In alternativa puoi ovviamente usare anche un'istruzione if ( if notfirst: print (new) else: notfirst = True ). Ma per quanto ne so il sovraccarico è più grande.


Using `timeit` yields:

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

quindi mi aspetto che l'overhead non sia selezionabile.

Contare gli oggetti una volta e tenere il passo con il numero di elementi rimanenti:

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

In questo modo si valuta la lunghezza dell'elenco una sola volta. Molte delle soluzioni in questa pagina sembrano supporre che la lunghezza non sia disponibile in anticipo, ma questo non fa parte della tua domanda. Se hai la lunghezza, usala.

Per me il modo più semplice e pitonico di gestire un caso speciale alla fine di un elenco è:

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

Naturalmente questo può anche essere usato per trattare il primo elemento in modo speciale.

Possono esserci più modi. l'affettare sarà più veloce. Aggiungendo un altro che utilizza il metodo .index ():

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top