我想创建两个列表listOfA
和listOfB
来存储来自其他列表的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=[--code goes here--]
答案 0 :(得分:36)
列表推导的定义是生成一个列表对象。你的2个列表对象的长度不同;你必须使用副作用来达到你想要的效果。
请勿在此处使用列表推导。只需使用普通循环:
listOfA, listOfB = [], []
for idx, x in enumerate(s):
target = listOfA if x == 'A' else listOfB
target.append(idx)
这使您只需执行一个循环;这将击败任何两个列表推导,至少在开发人员找到一种方法使列表推导构建列表的速度比具有单独list.append()
调用的循环快两倍之前。
我会在嵌套列表理解 的任何一天选择它,以便能够在一行上生成两个列表。正如Zen of Python所述:
可读性计数。
答案 1 :(得分:10)
排序;关键是生成一个2元素列表,然后解压缩:
listOfA, listOfB = [[idx for idx, x in enumerate(s) if x == c] for c in 'AB']
那就是说,我认为以这种方式做到这一点非常愚蠢,显式循环更具可读性。
答案 2 :(得分:5)
解决此问题的一个很好的方法是使用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])
答案 3 :(得分:2)
您要执行的操作并非完全不可能,这只是复杂的操作,可能很浪费。
如果要将一个可迭代对象划分为两个可迭代对象,如果源是列表或其他可重复使用的可迭代对象,则最好在两次通过后再进行处理(如您的问题)。
即使源是迭代器,如果想要的输出是一对列表,而不是一对惰性迭代器,请使用Martijn's answer或对list(iterator)
进行两次传递。)< / p>
但是,如果您确实需要将一个任意的迭代器懒散地划分为两个迭代器,那么没有某种中间存储就无法做到这一点。
假设您将[1, 2, -1, 3, 4, -2]
分为positives
和negatives
。现在,您尝试next(negatives)
。那应该给你-1
,对吧?但是,如果不消耗1
和2
,就无法做到这一点。这意味着当您尝试next(positives)
时,您将得到3
而不是1
。因此,1
和2
需要存储在某个地方。
您需要的大多数聪明之处都包含在itertools.tee
中。如果您只是将positives
和negatives
做成同一个迭代器的两个TED副本,然后将它们都过滤,就可以完成。
实际上,这是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
共享它们。它应该是几十行代码,不需要任何棘手的事情。)
您甚至可以从partition
之类的第三方库中获得more_itertools
作为导入。
现在,您可以单线使用它:
lst = [1, 2, -1, 3, 4, -2]
positives, negatives = partition(lst, lambda x: x>=0)
...,您已经在所有正值上进行了迭代,并在所有负值上进行了迭代。它们看起来好像是完全独立的,但是它们在一起只能进行一次lst
传递,因此即使您将lst
分配给生成器表达式或文件或其他东西而不是列表,它也可以工作。
那么,为什么没有某种快捷方式语法呢?因为这会产生误导。
理解力不需要额外的存储空间。这就是生成器表达式之所以如此出色的原因-它们可以将一个惰性迭代器转换为另一个惰性迭代器而无需存储任何内容。
但这需要O(N)
存储。想象所有数字都是正数,但是您尝试首先迭代negative
。怎么了?所有数字都被推送到trueq
。实际上,O(N)
甚至可以无限(例如,在itertools.count()
上尝试)。
对于itertools.tee
之类的东西来说,这很好,它是大多数新手甚至都不知道的模块中所包含的函数,并且其中有不错的文档可以解释其作用并弄清楚成本。但是,使用语法糖来使它看起来像正常的理解力却是另一回事。
答案 4 :(得分:0)
对于那些生活在边缘的人;)
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)])]