Doble iteración en la comprensión de la lista

226

En Python puedes tener múltiples iteradores en una lista de comprensión, como

[(x,y) for x in a for y in b]

para algunas secuencias adecuadas a y b. Soy consciente de la semántica de bucle anidado de las comprensiones de listas de Python.

Mi pregunta es: ¿puede un iterador en la comprensión referirse al otro? En otras palabras: ¿podría tener algo como esto?

[x for x in a for a in b]

donde el valor actual del bucle externo es el iterador del interno?

Como ejemplo, si tengo una lista anidada:

a=[[1,2],[3,4]]

¿Cuál sería la expresión de comprensión de la lista para lograr este resultado?

[1,2,3,4]

?? (Por favor, solo enumere las respuestas de comprensión, ya que esto es lo que quiero averiguar).

ThomasH
fuente

Respuestas:

178

Para responder a su pregunta con su propia sugerencia:

>>> [x for b in a for x in b] # Works fine

Si bien solicitó respuestas de comprensión de la lista, permítame señalar también los excelentes itertools.chain ():

>>> from itertools import chain
>>> list(chain.from_iterable(a))
>>> list(chain(*a)) # If you're using python < 2.6
Cide
fuente
11
[x for b in a for x in b]Esto siempre ha sido molesto sobre python. Esta sintaxis es muy al revés. La forma general de x for x in ysiempre tiene la variable directamente después de for, alimenta la expresión a la izquierda de for. Tan pronto como haces una doble comprensión, tu variable iterada más recientemente está repentinamente tan "lejos". Es incómodo y no se lee de forma natural en absoluto
Cruncher
170

¡Espero que esto ayude a alguien más ya a,b,x,yque no tiene mucho significado para mí! Supongamos que tiene un texto lleno de oraciones y desea una serie de palabras.

# Without list comprehension
list_of_words = []
for sentence in text:
    for word in sentence:
       list_of_words.append(word)
return list_of_words

Me gusta pensar en la comprensión de la lista como un código de estiramiento horizontal.

Intenta dividirlo en:

# List Comprehension 
[word for sentence in text for word in sentence]

Ejemplo:

>>> text = (("Hi", "Steve!"), ("What's", "up?"))
>>> [word for sentence in text for word in sentence]
['Hi', 'Steve!', "What's", 'up?']

Esto también funciona para generadores

>>> text = (("Hi", "Steve!"), ("What's", "up?"))
>>> gen = (word for sentence in text for word in sentence)
>>> for word in gen: print(word)
Hi
Steve!
What's
up?
Skam
fuente
8
"Solo hay dos problemas difíciles en Ciencias de la Computación: invalidación de caché y nombrar cosas". - Phil Karlton
cezar
¡Esta es una gran respuesta ya que hace que todo el problema sea menos abstracto! ¡Gracias!
A. Blesius
Me preguntaba, ¿puedes hacer lo mismo con tres niveles de abstracción en una lista de comprensión? ¿Te gustan los capítulos en texto, oraciones en capítulos y palabras en oraciones?
Capitán Fogetti
123

Caramba, creo que encontré la respuesta: no me estaba preocupando lo suficiente sobre qué bucle es interno y cuál es externo. La comprensión de la lista debería ser como:

[x for b in a for x in b]

para obtener el resultado deseado, y sí, un valor actual puede ser el iterador para el siguiente ciclo.

ThomasH
fuente
67
La sintaxis de comprensión de listas no es uno de los puntos brillantes de Python.
Glenn Maynard el
2
@Glenn Sí, fácilmente se complica por más que simples expresiones.
ThomasH
1
Ew. No estoy seguro de que este sea el uso "habitual" para la comprensión de listas, pero es muy lamentable que el encadenamiento sea tan desagradable en Python.
Matt Joiner
14
Se ve muy limpio si pones nuevas líneas antes de cada 'para'.
Nick Garvey
16
Wow, esto es completamente inverso a lo que tiene sentido en mi cabeza.
obskyr
51

El orden de los iteradores puede parecer contrario a la intuición.

Toma por ejemplo: [str(x) for i in range(3) for x in foo(i)]

Vamos a descomponerlo:

def foo(i):
    return i, i + 0.5

[str(x)
    for i in range(3)
        for x in foo(i)
]

# is same as
for i in range(3):
    for x in foo(i):
        yield str(x)
Dima Tisnek
fuente
44
¡Qué revelación!
nehem
Tengo entendido que la razón de esto es que "la primera iteración enumerada es la iteración más alta que se escribiría si la comprensión se escribiera como anidada para bucles". La razón por la que esto es contraintuitivo es que el bucle EXTERIOR (el más alto si está escrito como bucles for anidados) aparece en el INTERIOR de la lista / dict entre corchetes (objeto comprensible). Por el contrario, el bucle interno (más interno cuando se escribe como bucles for anidados) es precisamente el bucle más a la derecha en una comprensión, y de esa manera aparece en el EXTERIOR de la comprensión.
Zach Siegel
Escrito de forma abstracta tenemos [(output in loop 2) (loop 1) (loop 2)]con (loop 1) = for i in range(3)y (loop 2) = for x in foo(i):y (output in loop 2) = str(x).
Qaswed
20

ThomasH ya ha agregado una buena respuesta, pero quiero mostrar lo que sucede:

>>> a = [[1, 2], [3, 4]]
>>> [x for x in b for b in a]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

>>> [x for b in a for x in b]
[1, 2, 3, 4]
>>> [x for x in b for b in a]
[3, 3, 4, 4]

Supongo que Python analiza la comprensión de la lista de izquierda a derecha. Esto significa que el primer forbucle que ocurra se ejecutará primero.

El segundo "problema" de esto es que bse "filtra" de la comprensión de la lista. Después de la primera lista exitosa comprensión b == [3, 4].

Martin Thoma
fuente
3
Punto interesante Me sorprendió esto:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
grinch
2
Esta fuga se solucionó en Python 3: stackoverflow.com/questions/4198906/…
Denilson Sá Maia el
10

Si desea mantener la matriz multidimensional, debe anidar los corchetes de la matriz. vea el ejemplo a continuación donde se agrega uno a cada elemento.

>>> a = [[1, 2], [3, 4]]

>>> [[col +1 for col in row] for row in a]
[[2, 3], [4, 5]]

>>> [col +1 for row in a for col in row]
[2, 3, 4, 5]
Steven
fuente
8

Esta técnica de memoria me ayuda mucho:

[ <RETURNED_VALUE> <OUTER_LOOP1> <INNER_LOOP2> <INNER_LOOP3> ... <OPTIONAL_IF> ]

Y ahora puedes pensar en R eturn + O uter -loop como el único R ight O rden

Sabiendo lo anterior, el orden en la lista completa incluso para 3 bucles parece fácil:


c=[111, 222, 333]
b=[11, 22, 33]
a=[1, 2, 3]

print(
  [
    (i, j, k)                            # <RETURNED_VALUE> 
    for i in a for j in b for k in c     # in order: loop1, loop2, loop3
    if i < 2 and j < 20 and k < 200      # <OPTIONAL_IF>
  ]
)
[(1, 11, 111)]

porque lo anterior es solo un:

for i in a:                         # outer loop1 GOES SECOND
  for j in b:                       # inner loop2 GOES THIRD
    for k in c:                     # inner loop3 GOES FOURTH
      if i < 2 and j < 20 and k < 200:
        print((i, j, k))            # returned value GOES FIRST

para iterar una lista / estructura anidada, la técnica es la misma: por ala pregunta:

a = [[1,2],[3,4]]
[i2    for i1 in a      for i2 in i1]
which return [1, 2, 3, 4]

uno para el otro nivel anidado

a = [[[1, 2], [3, 4]], [[5, 6], [7, 8, 9]], [[10]]]
[i3    for i1 in a      for i2 in i1     for i3 in i2]
which return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

y así

Sławomir Lenart
fuente
Gracias, pero lo que usted describe es en realidad el caso simple donde los iteradores involucrados son independientes. De hecho, en su ejemplo, podría usar los iteradores en cualquier orden y obtendría la misma lista de resultados (ordenación de módulos). El caso en el que estaba más interesado fue en las listas anidadas donde un iterador se convierte en el iterable del siguiente.
ThomasH
@ThomasH: el orden del bucle definido en negrita es exactamente para su necesidad. En la parte inferior se agregó un ejemplo para cubrir sus datos y un ejemplo más con un nivel anidado adicional.
Sławomir Lenart
5

Siento que esto es más fácil de entender.

[row[i] for row in a for i in range(len(a))]

result: [1, 2, 3, 4]
Miao Li
fuente
3

Además, puede usar la misma variable para el miembro de la lista de entrada a la que se accede actualmente y para el elemento dentro de este miembro. Sin embargo, esto incluso podría hacerlo más (lista) incomprensible.

input = [[1, 2], [3, 4]]
[x for x in input for x in x]

Primero for x in inputse evalúa, lo que lleva a una lista de miembros de la entrada, luego, Python recorre la segunda parte for x in xdurante la cual el valor actual es sobrescrito por el elemento actual al que está accediendo, luego el primero xdefine lo que queremos devolver.

tonto como un asno
fuente
1

Esta función flatten_nlevel llama recursivamente a la lista1 anidada para que se convierta en un nivel. Probar esto

def flatten_nlevel(list1, flat_list):
    for sublist in list1:
        if isinstance(sublist, type(list)):        
            flatten_nlevel(sublist, flat_list)
        else:
            flat_list.append(sublist)

list1 = [1,[1,[2,3,[4,6]],4],5]

items = []
flatten_nlevel(list1,items)
print(items)

salida:

[1, 1, 2, 3, 4, 6, 4, 5]
ravibeli
fuente
1
Ok, la pregunta era particularmente sobre la comprensión de la lista, y el aplanamiento de la lista era solo un ejemplo. Pero supongo que su acoplador de lista generalizado debería llamarse recursivamente. Así que probablemente sea más flatten_nlevel(sublist, flat_list), ¿verdad?
ThomasH