リスト内包表記の二重反復


226

Pythonでは、次のように、リスト内包に複数のイテレータを含めることができます。

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

いくつかの適切なシーケンスaおよびbの場合。Pythonのリスト内包表記のネストされたループのセマンティクスを知っています。

私の質問は次のとおりです。内包の1つの反復子が他の反復子を参照できますか?言い換えれば、私はこのようなものを持つことができます:

[x for x in a for a in b]

外側のループの現在の値は内側のイテレータですか?

例として、ネストされたリストがある場合:

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

この結果を達成するためにリスト内包表記はどうなるでしょう:

[1,2,3,4]

?? (これは私が知りたいことなので、理解度の答えのみをリストしてください)。

回答:


178

自分の提案で質問に答えるには:

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

リストの理解力の回答を求めている間、優れたitertools.chain()も指摘しておきます。

>>> from itertools import chain
>>> list(chain.from_iterable(a))
>>> list(chain(*a)) # If you're using python < 2.6

11
[x for b in a for x in b]これは常にpythonに関するバグでした。この構文は非常に逆です。の一般的な形式でx for x in yは、常にforの直後に変数があり、forの左側の式にフィードされます。二重理解を行うとすぐに、最後に反復された変数が突然「遠く」になります。それは厄介だし、まったく自然に読んでいません
たけた

170

私にa,b,x,yはあまり意味がないので、これが他の人の役に立つことを願っています!文章でいっぱいのテキストがあり、単語の配列が必要だとします。

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

リストの理解は、コードを水平方向に引き伸ばすことと考えるのが好きです。

それを分割してみてください:

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

例:

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

これはジェネレーターでも機能します

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

8
「コンピューターサイエンスには、キャッシュの無効化と名前の付け方という2つの難しい問題しかありません。」-フィルカールトン
セザール

問題全体の抽象度が低くなるため、これは素晴らしい答えです。ありがとうございました!
A.ブレシウス

私は不思議に思っていましたが、リスト内包表記の3つの抽象化レベルで同じことができますか?テキストの章、章の文、文の単語のように?
キャプテンFogetti

123

ああ、私はアンサーを見つけたと思います。どちらのループが内側で、どちらのループが外側であるかについては十分注意していませんでした。リスト内包表記は次のようになります。

[x for b in a for x in b]

望ましい結果を得るには、はい、1つの現在の値を次のループのイテレータにすることができます。


67
リスト内包構文は、Pythonの優れた点の1つではありません。
グレンメイナード

2
@Glennええ、単純な表現以上に複雑になってしまいます。
ThomasH 2009

1
ええ。これがリスト内包表記の「通常の」使用法であるかどうかはわかりませんが、Pythonではチェーニングが非常に厄介であることは非常に残念です。
マットジョイナー

14
それぞれの「for」の前に改行を入れると、とてもきれいに見えます。
Nick Garvey

16
うわー、これは私の頭の中で理にかなっているものとは完全に逆です。
obskyr 2017年

51

イテレータの順序は直観に反するように見えるかもしれません。

例えば: [str(x) for i in range(3) for x in foo(i)]

それを分解しましょう:

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)

4
なんて目を見張るような!!
ネヘム2017年

私が理解しているのは、この理由は、「リストされている最初の反復は、内包表記がネストされたforループとして記述された場合に入力される最上位の反復である」ということです。これが直観に反する理由は、外側のループ(ネストされたforループとして記述されている場合は最上位)が括弧で囲まれたリスト/ dict(理解されたオブジェクト)の内側に表示されるためです。逆に、INNERループ(ネストされたforループとして記述されている場合は、最も内側)は、内包表記の最も右のループであり、内包表記の外側に表示されます。
Zach Siegel

抽象的に書かれているの[(output in loop 2) (loop 1) (loop 2)](loop 1) = for i in range(3)and (loop 2) = for x in foo(i):(output in loop 2) = str(x)です。
Qaswed

20

ThomasHはすでに良い答えを追加していますが、何が起こるかを示したいと思います。

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

Pythonはリスト内包表記を左から右に解析していると思います。つまり、最初にfor発生するループが最初に実行されます。

これの2番目の「問題」bは、リストの理解から「漏れ」てしまうことです。最初の成功したリストの理解の後b == [3, 4]


3
興味深い点。私はこれに驚きました:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
グリンチ2014年

2
この漏れは、Pythonの3で修正されました:stackoverflow.com/questions/4198906/...
デニウソンSáマイア

10

多次元配列を保持したい場合は、配列ブラケットをネストする必要があります。以下の例を参照してください。すべての要素に1つ追加されています。

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

8

このメモリ技術は私に大いに役立ちます:

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

そして今、あなたはR eturn + O uter-loopを唯一のR ight Oと考えることができます RDER

上記を知っていると、3つのループでも包括的なリスト内の順序は簡単に見えます。


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

上記は単なるaであるため:

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

1つのネストされたリスト/構造を反復する場合、技術は同じです:a質問から:

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

ネストされた別のレベル

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]

等々


ありがとう、しかし、あなたが説明するのは、実際には、関与する反復子が独立している単純なケースです。実際、あなたの例では、イテレータを任意の順序で使用でき、同じ結果リスト(モジュロ順序)を取得します。私がもっと興味を持ったのは、1つのイテレータが次のイテラブルになるネストされたリストの場合でした。
ThomasH

@ThomasH:太字で定義されたループの順序は、あなたのニーズにぴったりです。下部に、データをカバーする例と、ネストされたレベルを追加したもう1つの例を追加しました。
SławomirLenart

5

これは理解しやすいと思います

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

result: [1, 2, 3, 4]

3

また、あなたは現在、アクセスされた入力リストのメンバーのためだけの同じ変数を使用することができますし、このメンバー内の要素のために。ただし、これによりさらに(リスト)がわかりにくくなる場合もあります。

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

最初for x in inputに評価され、入力の1つのメンバーリストにつながります。次に、Pythonは2番目の部分をウォークスルーし、for x in xその間にx値はアクセスしている現在の要素によって上書きされます。次に最初の部分xが返すものを定義します。


1

このflatten_nlevel関数は、ネストされたlist1を再帰的に呼び出して、1つのレベルに変換します。これを試してください

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)

出力:

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

1
OK、問題は特にリストの理解についてであり、リストのフラット化は単なる例でした。しかし、私は、あなたの一般化されたリストのフラット化機能は、それ自体を再帰的に呼び出す必要があると思います。だから、たぶんに似ているでしょflatten_nlevel(sublist, flat_list)?!
ThomasH
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.