Iteração dupla na compreensão da lista

226

No Python, você pode ter vários iteradores em uma compreensão de lista, como

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

para algumas sequências adequadas a e b. Estou ciente da semântica de loop aninhado das compreensões de lista do Python.

Minha pergunta é: um iterador na compreensão pode se referir ao outro? Em outras palavras: eu poderia ter algo parecido com isto:

[x for x in a for a in b]

onde o valor atual do loop externo é o iterador do interno?

Como exemplo, se eu tiver uma lista aninhada:

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

qual seria a expressão de compreensão da lista para alcançar esse resultado:

[1,2,3,4]

?? (Por favor, liste apenas as respostas de compreensão, pois é isso que eu quero descobrir).

ThomasH
fonte

Respostas:

178

Para responder sua pergunta com sua própria sugestão:

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

Enquanto você solicitou respostas de compreensão da lista, deixe-me destacar também o excelente itertools.chain ():

>>> from itertools import chain
>>> list(chain.from_iterable(a))
>>> list(chain(*a)) # If you're using python < 2.6
Cide
fonte
11
[x for b in a for x in b]Isso sempre causou problemas em python. Essa sintaxe é muito atrasada. A forma geral de x for x in ysempre tem a variável diretamente após o for, alimenta a expressão à esquerda do for. Assim que você faz uma dupla compreensão, sua variável iterada mais recentemente fica subitamente tão "longe". É estranho, e não lê naturalmente
Cruncher
170

Espero que isso ajude outra pessoa, pois a,b,x,ynão tem muito significado para mim! Suponha que você tenha um texto cheio de frases e queira uma variedade de palavras.

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

Eu gosto de pensar na compreensão da lista como esticar o código horizontalmente.

Tente dividi-lo em:

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

Exemplo:

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

Isso também funciona para geradores

>>> 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
fonte
8
"Existem apenas dois problemas difíceis na Ciência da Computação: invalidação de cache e nomeação de coisas." - Phil Karlton
cezar
Essa é uma ótima resposta, pois torna todo o problema menos abstrato! Obrigado!
A. Blesius
Fiquei me perguntando, você pode fazer o mesmo com três níveis de abstração em uma lista de compreensão? Como capítulos em texto, frases em capítulos e palavras em frases?
Capitão Fogetti
123

Puxa, acho que encontrei a resposta: não estava cuidando o suficiente sobre qual loop é interno e qual é externo. A compreensão da lista deve ser como:

[x for b in a for x in b]

para obter o resultado desejado e sim, um valor atual pode ser o iterador para o próximo loop.

ThomasH
fonte
67
A sintaxe de compreensão de lista não é um dos pontos brilhantes do Python.
21119 Glenn Maynard
2
@ Glenn Sim, é facilmente complicado por mais do que expressões simples.
ThomasH
1
Ai credo. Não sei se esse é o uso "usual" para a compreensão de listas, mas é muito lamentável que o encadeamento seja tão desagradável no Python.
Matt Joiner
14
Parece muito limpo se você colocar novas linhas antes de cada 'para'.
Nick Garvey
16
Uau, isso é completamente contrário ao que faz sentido na minha cabeça.
obskyr
51

A ordem dos iteradores pode parecer contra-intuitiva.

Considere por exemplo: [str(x) for i in range(3) for x in foo(i)]

Vamos decompor:

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
fonte
4
Que revelador !!
Nehem
Meu entendimento é que a razão para isso é que "a primeira iteração listada é a iteração mais alta que seria digitada se a compreensão fosse escrita como aninhada para loops". A razão pela qual isso é contra-intuitivo é que o loop OUTER (acima de tudo, se escrito como loops for aninhados) aparece no INTERIOR da lista entre colchetes / dict (objeto compreendido). Por outro lado, o loop INNER (mais interno quando escrito como loops for-aninhados) é precisamente o loop mais à direita de uma compreensão e, dessa forma, aparece no lado de fora da compreensão.
Zach Siegel
Escrito abstratamente, temos [(output in loop 2) (loop 1) (loop 2)]com (loop 1) = for i in range(3)e (loop 2) = for x in foo(i):e (output in loop 2) = str(x).
qaswed
20

ThomasH já adicionou uma boa resposta, mas quero mostrar o que acontece:

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

Eu acho que Python analisa a compreensão da lista da esquerda para a direita. Isso significa que o primeiro forloop que ocorrer será executado primeiro.

O segundo "problema" disso é que b"vaza" da compreensão da lista. Após a primeira compreensão bem sucedida da lista b == [3, 4].

Martin Thoma
fonte
3
Ponto interessante. Fiquei surpreso com isso:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
grinch
2
Esse vazamento foi corrigido no Python 3: stackoverflow.com/questions/4198906/…
Denilson Sá Maia
10

Se você deseja manter a matriz multidimensional, deve-se aninhar os colchetes da matriz. veja o exemplo abaixo, onde um é adicionado 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
fonte
8

Essa técnica de memória me ajuda muito:

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

E agora você pode pensar em R egressar + O uter de circuito como o único R ight O rder

Sabendo acima, a ordem na lista abrangente, mesmo para três loops, 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 o acima é apenas um:

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 uma lista / estrutura aninhada, technic é o mesmo: para aa pergunta:

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

um para o outro nível aninhado

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]

e assim por diante

Sławomir Lenart
fonte
Obrigado, mas o que você descreve é ​​realmente o caso simples em que os iteradores envolvidos são independentes. De fato, no seu exemplo, você poderia usar os iteradores em qualquer ordem e obteria a mesma lista de resultados (ordem do módulo). O caso em que eu estava mais interessado foi em listas aninhadas, nas quais um iterador se torna iterável do próximo.
ThomasH
@ ThomasH: a ordem do loop definida em negrito é exatamente para sua necessidade. Na parte inferior, adicionou um exemplo para cobrir seus dados e mais um exemplo com nível extra aninhado.
Sławomir Lenart
5

Eu sinto que isso é mais fácil de entender

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

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

Além disso, você pode usar exatamente a mesma variável para o membro da lista de entrada atualmente acessada e para o elemento dentro desse membro. No entanto, isso pode até torná-lo mais incompreensível (lista).

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

Primeiro for x in inputé avaliado, levando a uma lista de membros da entrada; em seguida, o Python percorre a segunda parte, for x in xdurante a qual o valor x é sobrescrito pelo elemento atual que está acessando, depois o primeiro xdefine o que queremos retornar.

Pateta
fonte
1

Essa função flatten_nlevel chama recursivamente a lista aninhada1 para ocultar um nível. Experimente isso

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)

resultado:

[1, 1, 2, 3, 4, 6, 4, 5]
ravibeli
fonte
1
Ok, a pergunta era particularmente sobre compreensão de lista e o achatamento de lista era apenas um exemplo. Mas suponho que seu nivelador de lista generalizado precisaria se chamar recursivamente. Então provavelmente é mais flatten_nlevel(sublist, flat_list), né ?!
ThomasH