Двойная итерация в понимании списка

226

В Python вы можете иметь несколько итераторов в понимании списка, например

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

для некоторых подходящих последовательностей а и б. Я знаю семантику вложенных циклов в списках Python.

Мой вопрос: может ли один итератор в понимании ссылаться на другой? Другими словами: Могу ли я иметь что-то вроде этого:

[x for x in a for a in b]

где текущее значение внешнего цикла является итератором внутреннего?

Как пример, если у меня есть вложенный список:

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

каким должно быть выражение для понимания списка для достижения этого результата:

[1,2,3,4]

?? (Пожалуйста, перечислите только ответы на осмысление, так как это то, что я хочу узнать).

ThomasH
источник

Ответы:

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
Cide
источник
11
[x for b in a for x in b]Это всегда было связано с питоном. Этот синтаксис такой обратный. Общая форма x for x in yвсегда имеет переменную непосредственно после for, передает выражение слева от for. Как только вы сделаете двойное понимание, ваша последняя итеративная переменная внезапно окажется настолько «далекой». Это неловко и совсем не читается естественно
Cruncher
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?
Skam
источник
8
«В информатике есть только две серьезные проблемы: аннулирование кэша и присвоение имен». - Фил Карлтон
Цезарь
Это отличный ответ, так как делает всю проблему менее абстрактной! Спасибо!
А. Блесиус
Мне было интересно, вы можете сделать то же самое с тремя уровнями абстракции в понимании списка? Как главы в тексте, предложения в главах и слова в предложениях?
Капитан Фогетти
123

Ну и дела, я думаю, что нашел ответ: я не заботился о том, какая петля является внутренней, а какая внешней. Понимание списка должно быть таким:

[x for b in a for x in b]

чтобы получить желаемый результат, и да, одно текущее значение может быть итератором для следующего цикла.

ThomasH
источник
67
Синтаксис понимания списков не является одной из самых ярких точек Python.
Гленн Мейнард
2
@Glenn Да, это легко запутать не только в простых выражениях.
ThomasH
1
Еа. Я не уверен, что это «обычное» использование для понимания списков, но очень жаль, что в Python цепочка настолько неприятна.
Мэтт Джойнер
14
Это выглядит очень чисто, если вы ставите новые строки перед каждым «для».
Ник Гарви
16
Вау, это полностью противоположно тому, что имеет смысл в моей голове.
Обскир
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
Что за откровение !!
Нехем
Насколько я понимаю, причина этого в том, что «первая итерация в списке является самой верхней итерацией, которая была бы напечатана, если бы понимание было записано как вложенное для циклов». Причина, по которой это противоречит интуиции, заключается в том, что цикл OUTER (самый верхний, если он записан как вложенные циклы for) появляется во ВНУТРИ заключенного в квадратные скобки списка / dict (объект с пониманием). И наоборот, цикл INNER (самый внутренний, когда он записан как вложенные циклы for) - это как раз крайний правый цикл в понимании, и таким образом он появляется вне Вне понимания.
Зак Сигел,
Абстрактно написано у нас [(output in loop 2) (loop 1) (loop 2)]с (loop 1) = for i in range(3)а (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цикл, который происходит, будет выполнен первым.

Вторая «проблема» в том, что она b«просочилась» из списка. После первого успешного понимания списка b == [3, 4].

Мартин Тома
источник
3
Интересный момент. Я был удивлен этим:x = 'hello'; [x for x in xrange(1,5)]; print x # x is now 4
Grinch
2
Эта утечка была исправлена ​​в Python 3: stackoverflow.com/questions/4198906/…
Денилсон Са Майя
10

Если вы хотите сохранить многомерный массив, следует вложить скобки массива. см. пример ниже, где каждый добавляется к каждому элементу.

>>> 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 петли , как только 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)]

потому что выше просто:

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

для итерации одного вложенного списка / структуры, методика та же: для 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]

и так далее

Славомир Ленарт
источник
Спасибо, но на самом деле вы описываете простой случай, когда вовлеченные итераторы независимы. На самом деле, в вашем примере вы можете использовать итераторы в любом порядке и получить один и тот же список результатов (упорядочение по модулю). Случай, который меня больше всего интересовал, был с вложенными списками, где один итератор становится итеративным для следующего.
ThomasH
@ThomasH: порядок циклов, выделенный жирным шрифтом, именно для вас. Внизу добавлен пример для покрытия ваших данных и еще один пример с дополнительным вложенным уровнем.
Славомир Ленарт
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выполняется оценка, ведущая к одному списку элементов ввода, затем Python проходит по второй части, for x in xво время которой значение x перезаписывается текущим элементом, к которому он обращается, затем первое xопределяет, что мы хотим вернуть.

простофиля
источник
1

Эта функция flatten_nlevel рекурсивно вызывает вложенный список list1 для перехода на один уровень. Попробуйте это

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]
ravibeli
источник
1
Хорошо, вопрос был особенно о понимании списков, и выравнивание списков было только примером. Но я полагаю, ваш обобщитель списков должен был бы вызывать себя рекурсивно. Так что, наверное, больше похоже flatten_nlevel(sublist, flat_list), правда ?!
ThomasH