for-loop - utiliza - for en python 3
¿Cuál es la forma pitónica de detectar el último elemento en un bucle python ''for''? (17)
Me gustaría saber la mejor manera (más compacta y "pitónica") para hacer un tratamiento especial para el último elemento en un bucle for. Hay un fragmento de código que debería llamarse solo entre elementos, siendo suprimido 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 usar reduce
;)
¿No hay posibilidad de iterar sobre todos, sino del último elemento, y tratar al último fuera del ciclo? Después de todo, se crea un ciclo para hacer algo similar a todos los elementos sobre los que se repite; si un elemento necesita algo especial, no debería estar en el circuito.
(vea también esta pregunta: does-the-last-element-in-a-loop-deserve-a-separate-treatment )
EDITAR: dado que la pregunta es más sobre el "intermedio", el primer elemento es el especial, ya que no tiene predecesor, o el último elemento es especial, ya que no tiene sucesor.
Asumiendo la entrada como un iterador, aquí hay una forma de usar tee e izip desde 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.
Manifestació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
>>>
Aunque esa pregunta es bastante antigua, vine aquí a través de google y encontré una manera bastante simple: List slicing. Digamos que quiere poner un ''&'' 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 & 2 & 3''.
Cuente los artículos 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 forma, 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 forma parte de su pregunta. Si tienes la longitud, úsalo.
El ''código entre'' es un ejemplo del patrón Head-Tail .
Usted tiene un artículo, que es seguido por una secuencia de pares (entre, elemento). También puede ver esto como una secuencia de pares (elemento, entre) seguido de un elemento. En general, es más simple tomar el primer elemento como especial y todos los demás como el caso "estándar".
Además, para evitar la repetición del código, debe proporcionar una función u otro objeto para contener el código que no desea repetir. Incrustar una instrucción if en un bucle que siempre es falso, excepto una vez, es una especie de tontería.
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 una gran cantidad de ejecución desperdiciada de una condición if que siempre es falsa excepto una vez.
Esto es similar al enfoque de Ants Aasma pero sin usar el módulo itertools. También es un iterador rezagado que mira hacia adelante 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
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 preguntó, pero si la data_list es destructible, sugiero que muestre 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. Encuentro esta solución más elegante y luego trato con next ().
La mayoría de las veces es más fácil (y más barato) hacer que la primera iteración sea el caso especial en lugar de la última:
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 estás tratando de hacer. Por ejemplo, si está creando una cadena de una lista, es mejor usar str.join()
que usar un ciclo for
"con un caso especial".
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, tendrá que 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
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 al retrasar el procesamiento de una iteración. Para omitir hacer algo durante la primera iteración, simplemente capturo el error.
Por supuesto, necesita pensar un poco, para que el NameError
cuando lo desee.
También mantenga el `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 no exista el uso de:
try:
del new
except NameError:
pass
Alternativamente, también puede usar una instrucción if ( if notfirst: print(new) else: notfirst = True
). Pero hasta donde yo sé, la sobrecarga es más grande.
Using `timeit` yields:
...: try: new = ''test''
...: except NameError: pass
...:
100000000 loops, best of 3: 16.2 ns per loop
así que espero que la sobrecarga no sea elegible.
Me gusta el enfoque de @ethan-t, aunque while 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.'')
No hay nada incorrecto en su camino, a menos que tenga 100 000 bucles y desee guardar 100 000 declaraciones "si". En ese caso, puedes ir por ese camino:
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
Productos:
1
2
3
3
Pero realmente, en tu caso, siento que es excesivo.
En cualquier caso, probablemente tengas más suerte con el corte:
for item in iterable[:-1] :
print item
print "last :", iterable[-1]
#outputs
1
2
last : 3
o solo :
for item in iterable :
print item
print iterable[-1]
#outputs
1
2
3
last : 3
Eventualmente, una forma de KISS de hacer tus cosas, y eso funcionaría con cualquier iterable, incluyendo las que no tienen __len__
:
item = ''''
for item in iterable :
print item
print item
Ouputs:
1
2
3
3
Si siento que lo haría de esa manera, me parece simple.
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 es necesario saber la longitud de antemano. La implementación pairwise es de recetas 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
Retrasa el manejo especial del último elemento hasta después del ciclo.
>>> for i in (1, 2, 3):
... pass
...
>>> i
3
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 algún código de Django para sus etiquetas de plantilla y básicamente hacen lo que estás haciendo.
Use cortar y is
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 en la lista son realmente diferentes (tienen diferentes ubicaciones en la memoria). Bajo el capó, Python puede detectar elementos iguales y reutilizar los mismos objetos para ellos. Por ejemplo, para cadenas del mismo valor y números enteros comunes.
si los artículos 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
si revisas la lista, para mí esto también funcionó:
for j in range(0, len(Array)):
if len(Array) - j > 1:
notLast()