列表理解中的双重迭代
Posted
技术标签:
【中文标题】列表理解中的双重迭代【英文标题】:Double Iteration in List Comprehension 【发布时间】:2010-11-14 23:42:51 【问题描述】:在 Python 中,您可以在列表推导中拥有多个迭代器,例如
[(x,y) for x in a for y in b]
对于一些合适的序列a和b。我知道 Python 列表推导的嵌套循环语义。
我的问题是:理解中的一个迭代器可以引用另一个吗?换句话说:我能有这样的东西吗:
[x for x in a for a in b]
外循环的当前值在哪里是内循环的迭代器?
例如,如果我有一个嵌套列表:
a=[[1,2],[3,4]]
为了达到这个结果,列表理解表达式是什么:
[1,2,3,4]
?? (请只列出理解答案,因为这是我想知道的)。
【问题讨论】:
【参考方案1】:哎呀,我想我找到了答案:我没有足够注意哪个循环是内部的,哪个是外部的。列表理解应该是这样的:
[x for b in a for x in b]
为了得到想要的结果,是的,一个当前值可以作为下一个循环的迭代器。
【讨论】:
列表解析语法不是 Python 的亮点之一。 @Glenn 是的,它很容易因为简单的表达式而变得复杂。 Ew。我不确定这是列表推导的“通常”用途,但不幸的是,Python 中的链接是如此讨厌。 如果你在每个'for'之前加上换行符,它看起来很干净。 哇,这与我的想法完全相反。【参考方案2】:用您自己的建议回答您的问题:
>>> [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
【讨论】:
[x for b in a for x in b]
这一直是关于 python 的问题。这种语法太落后了。 x for x in y
的一般形式总是在 for 之后直接包含变量,馈送到 for 左侧的表达式。一旦您进行双重理解,您最近迭代的变量就会突然变得如此“远”。这很尴尬,根本不自然地阅读【参考方案3】:
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]
。
【讨论】:
有趣的地方。我对此感到惊讶:x = 'hello';
[x for x in xrange(1,5)];
print x # x is now 4
此泄漏已在 Python 3 中修复:***.com/questions/4198906/…【参考方案4】:
我觉得这样比较容易理解
[row[i] for row in a for i in range(len(a))]
result: [1, 2, 3, 4]
【讨论】:
【参考方案5】:我希望这对其他人有所帮助,因为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?
【讨论】:
“计算机科学中只有两个难题:缓存失效和命名事物。” ——菲尔·卡尔顿 这是一个很好的答案,因为它使整个问题不那么抽象!谢谢! 或者,您可以:[[word for word in sentence] for sentence in text] @Saskia 不完全是。这只会给你同样的输入。你明白为什么了吗? 我只是建议一种语法来循环两个推导式,而不需要这种向后逻辑。与解决原始问题无关。【参考方案6】:迭代器的顺序可能看起来违反直觉。
例如:[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)
【讨论】:
真是大开眼界!! 我的理解是,这样做的原因是“列出的第一个迭代是如果将理解编写为嵌套 for 循环将键入的最顶层迭代”。这是违反直觉的原因是 OUTER 循环(如果写为嵌套的 for 循环,则最顶层)出现在括号列表/dict(可理解的对象)的 INSIDE 中。相反,INNER 循环(当写成嵌套的 for 循环时最内层)恰好是推导式中最右边的循环,并且以这种方式出现在推导式的 OUTSIDE。 抽象地写我们有[(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)
。【参考方案7】:
如果要保留多维数组,则应嵌套数组括号。请参阅下面的示例,其中每个元素都添加了一个。
>>> 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】:此外,您可以将相同的变量用于当前访问的输入列表的成员和用于该成员内的元素。但是,这甚至可能使其(列表)更加难以理解。
input = [[1, 2], [3, 4]]
[x for x in input for x in x]
首先评估for x in input
,导致输入的一个成员列表,然后,Python 遍历第二部分for x in x
,在此期间 x 值被它正在访问的当前元素覆盖,然后是第一个 @ 987654324@ 定义了我们想要返回的内容。
【讨论】:
【参考方案9】:这种记忆技术对我帮助很大:
[ <RETURNED_VALUE> <OUTER_LOOP1> <INNER_LOOP2> <INNER_LOOP3> ... <OPTIONAL_IF> ]
现在你可以考虑 Return + Outer-loop 作为唯一的Right Order
知道上面的内容,即使是 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:以粗体定义的循环顺序完全符合您的需要。在底部添加了一个示例来覆盖您的数据,并添加了一个具有额外嵌套级别的示例。【参考方案10】:这个 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]
【讨论】:
好的,这个问题特别是关于列表理解的,列表展平只是一个例子。但我认为,您的广义列表展平器需要递归调用自身。所以它可能更像flatten_nlevel(sublist, flat_list)
,对吧?!【参考方案11】:
在我第一次尝试时,我永远无法编写双重列表理解。读到PEP202,原来是因为它是以相反的方式实现的,你会用英文阅读它。好消息是它是一个逻辑合理的实现,所以一旦你理解了结构,就很容易做对。
设a、b、c、d为依次嵌套的对象。对我来说,扩展列表理解的直观方法是模仿英语:
# works
[f(b) for b in a]
# does not work
[f(c) for c in b for b in a]
[f(c) for c in g(b) for b in a]
[f(d) for d in c for c in b for b in a]
换句话说,你会从下往上阅读,即
# wrong logic
(((d for d in c) for c in b) for b in a)
然而,这不是 Python 实现嵌套列表的方式。相反,该实现将第一个块视为完全独立的,然后将 for
s 和 in
s 从上到下(而不是自下而上)链接在一个块中,即
# right logic
d: (for b in a, for c in b, for d in c)
请注意,最深的嵌套级别 (for d in c
) 离列表中的最终对象 (d
) 最远。原因来自from Guido himself:
[... for x... for y...]
形式嵌套,最后一个索引变化最快,就像嵌套 for 循环一样。
使用 Skam 的文本示例,这变得更加清晰:
# word: for sentence in text, for word in sentence
[word for sentence in text for word in sentence]
# letter: for sentence in text, for word in sentence, for letter in word
[letter for sentence in text for word in sentence for letter in word]
# letter:
# for sentence in text if len(sentence) > 2,
# for word in sentence[0],
# for letter in word if letter.isvowel()
[letter for sentence in text if len(sentence) > 2 for word in sentence[0] for letter in word if letter.isvowel()]
【讨论】:
次要问题:您的第一个代码部分中的第一个示例确实有效 ([f(b) for b in a]
)。
@ThomasH 更新:)以上是关于列表理解中的双重迭代的主要内容,如果未能解决你的问题,请参考以下文章