Pregunta

Me gustaría saber la mejor manera (más compacta y '' pitónica '') de hacer un tratamiento especial para el último elemento en un bucle for. Hay un código que debe llamarse solo entre elementos, que se suprime en el último.

Así es como lo hago actualmente:

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

¿Hay alguna forma mejor?

Nota: no quiero hacerlo con hacks como el uso de reduce ;)

¿Fue útil?

Solución

La mayoría de las veces es más fácil (y más barato) hacer que la iteración primera sea el caso especial en lugar del último:

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

    item()

Esto funcionará para cualquier iterable, incluso para aquellos que no tienen len () :

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

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

Aparte de eso, no creo que haya una solución generalmente superior, ya que depende de lo que intente hacer. Por ejemplo, si está creando una cadena a partir de una lista, es naturalmente mejor usar str.join () que usar un bucle for "con mayúsculas y minúsculas".


Usando el mismo principio pero más compacto:

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

Parece familiar, ¿no? :)


Para @ofko, y otros que realmente necesitan averiguar si el valor actual de un iterable sin len () es el último, deberá mirar hacia adelante:

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

Entonces puedes usarlo así:

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

Otros consejos

El 'código entre' es un ejemplo del patrón Head-Tail .

Tiene un elemento, que es seguido por una secuencia de pares (entre elementos). También puede ver esto como una secuencia de pares (elemento, entre) seguidos de un elemento. En general, es más simple tomar el primer elemento como especial y todos los demás como el "estándar". caso.

Además, para evitar repetir el código, debe proporcionar una función u otro objeto que contenga el código que no desea repetir. Incrustar una declaración if en un bucle que siempre es falso, excepto que una vez es un poco tonto.

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 )

Esto es más confiable porque es un poco más fácil de probar, no crea una estructura de datos adicional (es decir, una copia de una lista) y no requiere mucha ejecución desperdiciada de un si condición que siempre es falsa excepto una vez.

Si simplemente está buscando modificar el último elemento en data_list , simplemente puede usar la notación:

L[-1]

Sin embargo, parece que estás haciendo más que eso. No hay nada realmente malo en tu camino. Incluso eché un vistazo rápido a algunos código Django para sus etiquetas de plantilla y hacen básicamente lo que estás haciendo.

Aunque esa pregunta es bastante antigua, vine aquí a través de Google y encontré una forma bastante simple: cortar en listas. Digamos que quieres poner un '& amp;' entre todas las entradas de la lista.

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

Esto devuelve '1 & amp; 2 y amperios; 3 '.

Esto es similar al enfoque de Ants Aasma pero sin usar el módulo itertools. También es un iterador rezagado que anticipa un único elemento en la secuencia del iterador:

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 los elementos son únicos:

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

otras opciones:

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

Puede usar una ventana deslizante sobre los datos de entrada para echar un vistazo al siguiente valor y usar un centinela para detectar el último valor. Esto funciona en cualquier iterable, por lo que no necesita saber la longitud de antemano. La implementación por pares es de recetas de 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

¿No hay posibilidad de iterar sobre todo menos el último elemento y tratar el último fuera del bucle? Después de todo, se crea un bucle para hacer algo similar a todos los elementos sobre los que se bucle; si un elemento necesita algo especial, no debería estar en el ciclo.

(vea también esta pregunta: does -el-último-elemento-en-un-bucle-merece-un-tratamiento-separado )

EDITAR: dado que la pregunta es más acerca del " entre " ;, o bien el elemento primero es el especial porque no tiene predecesor, o el último El elemento es especial porque no tiene sucesor.

No hay nada de malo en su camino, a menos que tenga 100 000 bucles y quiera guardar 100 000 " if " declaraciones En ese caso, puede ir de esa manera:

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

Salidas:

1
2
3
3

Pero realmente, en tu caso siento que es excesivo.

En cualquier caso, probablemente tendrá más suerte con el corte:

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

#outputs
1
2
last : 3

o simplemente:

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

#outputs
1
2
3
last : 3

Eventualmente, una forma de KISS de hacer sus cosas, y eso funcionaría con cualquier iterable, incluidos los que no tienen __len__ :

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

Ouputs:

<*>

Si siento que lo haría de esa manera, me parece simple.

Use segmentación y es para verificar el último 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>

Caveat emptor : esto solo funciona si todos los elementos de la lista son realmente diferentes (tienen diferentes ubicaciones en la memoria). Debajo del capó, Python puede detectar elementos iguales y reutilizar los mismos objetos para ellos. Por ejemplo, para cadenas del mismo valor y enteros comunes.

Google me trajo a esta vieja pregunta y creo que podría agregar un enfoque diferente a este problema.

La mayoría de las respuestas aquí tratarían con un tratamiento adecuado de un control de bucle for como se le preguntó, pero si data_list es destructible, sugeriría que extraiga los elementos de la lista hasta que termine con una lista vacía :

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

incluso podría usar while len (element_list) si no necesita hacer nada con el último elemento. Esta solución me parece más elegante que tratar con next ().

Me gusta el enfoque de @ ethan-t, pero mientras True es peligroso desde mi punto de vista.

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

Retrasar el manejo especial del último elemento hasta después del ciclo.

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

Asumiendo la entrada como un iterador, esta es una forma de usar tee e izip de 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.

Demostración:

>>> 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 está revisando la lista, para mí esto también funcionó:

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

La solución más simple que viene a mi mente es:

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

Por lo tanto, siempre miramos hacia adelante un elemento retrasando el proceso una iteración. Para omitir hacer algo durante la primera iteración, simplemente capto el error.

Por supuesto, debe pensar un poco, para que el NameError se genere cuando lo desee.

También mantenga la `counstruct

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

Esto se basa en que el nombre nuevo no se definió previamente. Si eres paranoico, puedes asegurarte de que new no exista usando:

try:
    del new
except NameError:
    pass

Alternativamente, por supuesto, también puede usar una instrucción if ( if notfirst: print (new) else: notfirst = True ). Pero hasta donde yo sé, la sobrecarga es mayor.


Using `timeit` yields:

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

así que espero que los gastos generales sean ilegibles.

Cuente los elementos una vez y manténgase al día con la cantidad de elementos restantes:

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

De esta manera solo evalúa la longitud de la lista una vez. Muchas de las soluciones en esta página parecen suponer que la longitud no está disponible de antemano, pero eso no es parte de su pregunta. Si tiene la longitud, úsela.

Para mí, la forma más simple y pitónica de manejar un caso especial al final de una lista es:

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

Por supuesto, esto también se puede usar para tratar el primer elemento de una manera especial.

Puede haber múltiples formas. rebanar será más rápido. Agregar uno más que utiliza el método .index ():

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top