用于创建多个列表的 Python 列表推导式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21023482/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 21:49:35  来源:igfitidea点击:

Python list comprehensions to create multiple lists

pythonlistlist-comprehension

提问by Heisenberg

I want to create two lists listOfAand listOfBto store indices of Aand Bfrom another list s.

我想创建两个列表listOfAlistOfB存储的索引AB来自另一个列表秒。

s=['A','B','A','A','A','B','B']

Output should be two lists

输出应该是两个列表

listOfA=[0,2,3,4]
listOfB=[1,5,6]

I am able to do this with two statements.

我可以用两个语句来做到这一点。

listOfA=[idx for idx,x in enumerate(s) if x=='A']
listOfB=[idx for idx,x in enumerate(s) if x=='B']

However, I want to do it in only one iteration using list comprehensions only. Is it possible to do it in a single statement? something like listOfA,listOfB=[--code goes here--]

但是,我只想使用列表推导式在一次迭代中完成它。是否可以在单个语句中完成?就像是listOfA,listOfB=[--code goes here--]

采纳答案by Martijn Pieters

The very definition of a list comprehension is to produce onelist object. Your 2 list objects are of different lengths even; you'd have to use side-effects to achieve what you want.

列表推导式的定义就是生成一个列表对象。您的 2 个列表对象的长度甚至不同;你必须使用副作用来实现你想要的。

Don't use list comprehensions here. Just use an ordinary loop:

不要在这里使用列表推导式。只需使用一个普通的循环:

listOfA, listOfB = [], []

for idx, x in enumerate(s):
    target = listOfA if x == 'A' else listOfB
    target.append(idx)

This leaves you with just oneloop to execute; this will beat any two list comprehensions, at least not until the developers find a way to make list comprehensions build a list twice as fast as a loop with separate list.append()calls.

这让您只需要执行一个循环;这将击败任何两个列表理解,至少在开发人员找到一种方法使列表理解构建列表的速度是具有单独list.append()调用的循环的两倍之前。

I'd pick this any day over a nested list comprehension justto be able to produce two lists on one line. As the Zen of Pythonstates:

我会在任何一天通过嵌套列表理解来选择这个,只是为了能够在一行中生成两个列表。正如Python所说:

Readability counts.

可读性很重要。

回答by RemcoGerlich

Sort of; the key is to generate a 2-element list that you can then unpack:

有点; 关键是生成一个 2 元素列表,然后您可以将其解压缩:

listOfA, listOfB = [[idx for idx, x in enumerate(s) if x == c] for c in 'AB']

That said, I think it's pretty daft to do it that way, an explicit loop is much more readable.

也就是说,我认为这样做很愚蠢,显式循环更具可读性。

回答by Abhijit

A nice approach to this problem is to use defaultdict. As @Martin already said, list comprehension is not the right tool to produce two lists. Using defaultdict would enable you to create segregation using a single iteration. Moreover your code would not be limited in any form.

解决这个问题的一个好方法是使用 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])

回答by abarnert

What you're trying to do isn't exactly impossible, it's just complicated, and probably wasteful.

您尝试做的事情并非完全不可能,只是很复杂,而且可能很浪费。

If you want to partition an iterable into two iterables, if the source is a list or other re-usable iterable, you're probably better off either doing it in two passes, as in your question.

如果您想将一个可迭代对象划分为两个可迭代对象,如果源是一个列表或其他可重复使用的可迭代对象,那么您最好分两次进行,就像您的问题一样。

Even if the source is an iterator, if the output you want is a pair of lists, not a pair of lazy iterators, either use Martijn's answer, or do two passes over list(iterator).)

即使源是一个迭代器,如果你想要的输出是一对列表,而不是一对惰性迭代器,要么使用Martijn 的答案,要么做两次传递list(iterator)。)

But if you really need to lazily partition an arbitrary iterable into two iterables, there's no way to do that without some kind of intermediate storage.

但是,如果您真的需要将一个任意可迭代对象延迟地划分为两个可迭代对象,则没有某种中间存储就无法做到这一点。

Let's say you partition [1, 2, -1, 3, 4, -2]into positivesand negatives. Now you try to next(negatives). That ought to give you -1, right? But it can't do that without consuming the 1and the 2. Which means when you try to next(positives), you're going to get 3instead of 1. So, the 1and 2need to get stored somewhere.

假设您划分[1, 2, -1, 3, 4, -2]positivesnegatives。现在你尝试next(negatives)。那应该给你-1,对吧?但是如果不消耗1和 ,它就无法做到这一点2。这意味着当您尝试时next(positives),您将得到3而不是1。所以,12需要存储在某个地方。

Most of the cleverness you need is wrapped up inside itertools.tee. If you just make positivesand negativesinto two teed copies of the same iterator, then filter them both, you're done.

您需要的大部分聪明才智都包含在里面itertools.tee。如果您只是将positivesnegatives放入同一个迭代器的两个 teed 副本中,然后将它们都过滤,就完成了。

In fact, this is one of the recipes in the itertoolsdocs:

事实上,这是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)

(If you can't understand that, it's probably worth writing it out explicitly, with either two generator functions sharing an iterator and a tee via a closure, or two methods of a class sharing them via self. It should be a couple dozen lines of code that doesn't require anything tricky.)

(如果你不明白这一点,可能值得明确地写出来,要么两个生成器函数通过闭包共享一个迭代器和一个 tee,要么一个类的两个方法通过 共享它们self。它应该是几十行不需要任何棘手的代码。)

And you can even get partitionas an import from a third-party library like more_itertools.

您甚至可以partition从第三方库(如more_itertools.



Now, you can use this in a one-liner:

现在,您可以在单行中使用它:

lst = [1, 2, -1, 3, 4, -2]
positives, negatives = partition(lst, lambda x: x>=0)

… and you've got an iterator over all the positive values, and an iterator over all of the negative values. They look like they're completely independent, but together they only do a single pass over lst—so it works even if you assign lstto a generator expression or a file or something instead of a list.

... 你有一个遍历所有正值的迭代器和一个遍历所有负值的迭代器。它们看起来是完全独立的,但它们一起只做一次传递——lst因此即使您分配lst给生成器表达式或文件或其他东西而不是列表,它也能工作。



So, why isn't there some kind of shortcut syntax for this? Because it would be pretty misleading.

那么,为什么没有某种快捷语法呢?因为这会引起很大的误导。

A comprehension takes no extra storage. That's the reason generator expressions are so great—they can transform a lazy iterator into another lazy iterator without storing anything.

理解不需要额外的存储。这就是生成器表达式如此出色的原因——它们可以将一个惰性迭代器转换为另一个惰性迭代器,而无需存储任何内容。

But this takes O(N)storage. Imagine all of the numbers are positive, but you try to iterate negativefirst. What happens? All of the numbers get pushed to trueq. In fact, that O(N)could even be infinite(e.g., try it on itertools.count()).

但这需要O(N)存储。想象一下所有的数字都是正数,但你尝试先迭代negative。发生什么了?所有数字都被推到trueq. 事实上,这O(N)甚至可以是无限的(例如,试一试itertools.count())。

That's fine for something like itertools.tee, a function stuck in a module that most novices don't even know about, and which has nice docs that can explain what it does and make the costs clear. But doing it with syntactic sugar that made it look just like a normal comprehension would be a different story.

这对于类似的东西来说很好itertools.tee,一个大多数新手甚至都不知道的模块中的函数,并且有很好的文档可以解释它的作用并明确成本。但是用语法糖来做,让它看起来就像一个正常的理解,那就是另一回事了。

回答by Daniel Braun

For those who live on the edge ;)

对于那些生活在边缘的人;)

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