Python列表理解,用于创建多个列表
我想创建两个列表Python列表理解,用于创建多个列表,python,list,list-comprehension,Python,List,List Comprehension,我想创建两个列表listOfA和listOfB,以存储另一个列表s中A和B的索引 s=['A','B','A','A','A','B','B'] 输出应该是两个列表 listOfA=[0,2,3,4] listOfB=[1,5,6] 我可以用两种说法来说明这一点 listOfA=[idx for idx,x in enumerate(s) if x=='A'] listOfB=[idx for idx,x in enumerate(s) if x=='B'] 然而,我只想在一次迭代中使用列
listOfA
和listOfB
,以存储另一个列表s中A
和B
的索引
s=['A','B','A','A','A','B','B']
输出应该是两个列表
listOfA=[0,2,3,4]
listOfB=[1,5,6]
我可以用两种说法来说明这一点
listOfA=[idx for idx,x in enumerate(s) if x=='A']
listOfB=[idx for idx,x in enumerate(s) if x=='B']
然而,我只想在一次迭代中使用列表理解来完成它。
可以用一句话来表达吗?
类似于listOfA,listOfB=[--代码在这里--]某种程度上;关键是生成一个2元素列表,然后可以将其解包:
listOfA, listOfB = [[idx for idx, x in enumerate(s) if x == c] for c in 'AB']
也就是说,我认为这样做很愚蠢,显式循环更具可读性。列表理解的定义就是生成一个列表对象。您的两个列表对象的长度甚至不同;你必须使用副作用来达到你想要的效果 这里不要使用列表理解。只需使用普通循环:
listOfA, listOfB = [], []
for idx, x in enumerate(s):
target = listOfA if x == 'A' else listOfB
target.append(idx)
这只剩下一个要执行的循环;这将击败任何两个列表理解,至少在开发人员找到一种方法使列表理解构建一个列表的速度比使用单独的list.append()
调用的循环快两倍之前是如此
为了能够在一行中生成两个列表,我会在嵌套列表中选择这个。作为缔约国:
可读性很重要
解决这个问题的一个好方法是使用defaultdict。正如@Martin已经说过的,列表理解不是生成两个列表的正确工具。使用defaultdict将使您能够使用单个迭代创建隔离。此外,您的代码不会受到任何形式的限制
>>> from collections import defaultdict
>>> s=['A','B','A','A','A','B','B']
>>> listOf = defaultdict(list)
>>> for idx, elem in enumerate(s):
listOf[elem].append(idx)
>>> listOf['A'], listOf['B']
([0, 2, 3, 4], [1, 5, 6])
你想做的并不是完全不可能,只是很复杂,而且可能是浪费 如果您想将一个iterable划分为两个iterable,如果源代码是一个列表或其他可重用的iterable,那么您可能最好分两步进行,就像您的问题一样 即使源代码是迭代器,如果您想要的输出是一对列表,而不是一对惰性迭代器,也可以使用,或者在
list(迭代器)
上执行两次传递
但是,如果您真的需要将任意iterable惰性地划分为两个iterable,那么如果没有某种中间存储,就无法做到这一点
假设您将[1,2,-1,3,4,-2]
划分为正片
和负片
。现在您尝试下一步(否定)
。这应该给你-1
,对吗?但是如果不使用1
和2
,它就无法做到这一点。这意味着当您尝试下一步(积极)时,您将得到3
,而不是1
。因此,1
和2
需要存储在某个地方
你所需要的大部分聪明都被包裹在里面。如果您只是将正片
和负片
制作成同一迭代器的两个T型副本,然后对它们进行过滤,那么就完成了
事实上,这是itertools
文档中的一个配方:
def partition(pred, iterable):
'Use a predicate to partition entries into false entries and true entries'
# partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9
t1, t2 = tee(iterable)
return filterfalse(pred, t1), filter(pred, t2)
(如果您不理解这一点,可能值得明确地写出它,两个生成器函数通过闭包共享一个迭代器和一个tee,或者一个类的两个方法通过self
共享它们。应该是几十行代码,不需要任何复杂的操作。)
您甚至可以从第三方库(如more\u itertools
)导入分区
现在,您可以在一行程序中使用此选项:
lst = [1, 2, -1, 3, 4, -2]
positives, negatives = partition(lst, lambda x: x>=0)
…所有正值都有一个迭代器,所有负值都有一个迭代器。它们看起来是完全独立的,但它们一起只传递一次lst
——因此,即使将lst
指定给生成器表达式、文件或其他对象而不是列表,它也可以工作
那么,为什么没有一些快捷语法呢?因为这会让人产生误解
理解不需要额外的存储。这就是生成器表达式如此强大的原因,它们可以将一个惰性迭代器转换为另一个惰性迭代器,而无需存储任何内容
但这需要O(N)
存储。假设所有的数字都是正数,但您首先尝试迭代负数。会发生什么?所有数字都被推送到trueq
。事实上,O(N)
甚至可能是无限的(例如,在itertools.count()
上尝试它)
这对于像itertools.tee
这样的函数来说是很好的,这是一个卡在大多数新手都不知道的模块中的函数,它有很好的文档,可以解释它的功能并明确成本。但是,如果使用语法糖使它看起来像一个正常的理解,那么情况就不同了。对于那些生活在边缘的人;)
这仍然循环两次,而且非常不可读。他还说他想要一次迭代,而不仅仅是一条语句(这仍然会迭代s
两次)。@Martin的解决方案似乎更好,因为它只迭代一次列表理解(生成一个列表),比通过追加生成列表更快?@Heisenberg:是的,因为Python可以完全用C语言构建列表。没有讨厌的Python堆栈推送和弹出,没有.append()
属性查找。我们可以稍微优化后者(在循环外使用a,B=listOfA.append,listOfB.append
并重用它们),但是堆栈调用仍然比C代码慢。为什么?您是否担心2*N的复杂性?O(2N)≈ O(N)。我会认真考虑使用两个与你的列表理解非常相似的生成器。KojiRo:这里没有复杂的问题,我只想探索Python的特性。对于两个键,我会把钱放在我的条件语句上,打败你的<代码>散列(ELEM)。
呼叫。@MartijnPieters:我不会和你打赌的。我只是提供了一个阿尔特
listOfA, listOfB = [[i for i in cur_list if i is not None] for cur_list in zip(*[(idx,None) if value == 'A' else (None,idx) for idx,value in enumerate(s)])]