列表推导式:为每个项返回两个(或更多)项

138

能否在列表推导式中为每个项返回2个或多个项?

我想要的示例:

[f(x), g(x) for x in range(n)]

应该返回[f(0), g(0), f(1), g(1), ..., f(n-1), g(n-1)]

因此,需要替换这个代码块:

result = list()
for x in range(n):
    result.add(f(x))
    result.add(g(x))

3
出于好奇,你为什么想要这样做?也许有更好的方法来实现你的最终目标,而不是试图用这种方式来完成。 - murgatroid99
5
主要是因为我喜欢函数式编程。我想将一系列坐标映射为屏幕坐标元组,以便与pyglet.graphics.draw函数一起使用。 - Hashmush
屏幕坐标?就是XY坐标吗?你为什么想要将它们分开? - ddejohn
8个回答

189

双重列表推导:

[f(x) for x in range(5) for f in (f1,f2)]

演示:

>>> f1 = lambda x: x
>>> f2 = lambda x: 10*x

>>> [f(x) for x in range(5) for f in (f1,f2)]
[0, 0, 1, 10, 2, 20, 3, 30, 4, 40]

24
这很好,因为它表明双重列表推导并不可怕:它们只是嵌套的for循环,写法与普通的for循环一样。 for x in range(5): for f in (f1, f2): newlist.append(f(x))。我曾经觉得它们有点令人困惑,因为我一直试图颠倒顺序。 - DSM
3
这应该是被接受的答案,谢谢,太棒了! - Wingjam
@DSM 我认为,这将永远令人困惑。 - Winand
非常优雅,这就是我要选择的答案!! - Malaney

66
>>> from itertools import chain
>>> f = lambda x: x + 2
>>> g = lambda x: x ** 2
>>> list(chain.from_iterable((f(x), g(x)) for x in range(3)))
[2, 0, 3, 1, 4, 4]

时间:

from timeit import timeit

f = lambda x: x + 2
g = lambda x: x ** 2

def fg(x):
    yield f(x)
    yield g(x)

print timeit(stmt='list(chain.from_iterable((f(x), g(x)) for x in range(3)))',
             setup='gc.enable(); from itertools import chain; f = lambda x: x + 2; g = lambda x: x ** 2')

print timeit(stmt='list(chain.from_iterable(fg(x) for x in range(3)))',
             setup='gc.enable(); from itertools import chain; from __main__ import fg; f = lambda x: x + 2; g = lambda x: x ** 2')

print timeit(stmt='[func(x) for x in range(3) for func in (f, g)]',
             setup='gc.enable(); f = lambda x: x + 2; g = lambda x: x ** 2')


print timeit(stmt='list(chain.from_iterable((f(x), g(x)) for x in xrange(10**6)))',
             setup='gc.enable(); from itertools import chain; f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

print timeit(stmt='list(chain.from_iterable(fg(x) for x in xrange(10**6)))',
             setup='gc.enable(); from itertools import chain; from __main__ import fg; f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

print timeit(stmt='[func(x) for x in xrange(10**6) for func in (f, g)]',
             setup='gc.enable(); f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

2.69210777094

3.13900787874

1.62461071932

25.5944058287

29.2623711793

25.7211849286


5
这段代码创建了不必要的元组(f(x), g(x))。可以改写成以下方式:def fg(x): yield x + 2; yield x ** 2; list(chain.from_iterable(fg(x) for x in range(3))) - khachik
1
你甚至可以使用 chain.from_iterable((func(x) for func in funcs) for x in range(n))) 来进行泛化,这将消除khachik的抱怨。(虽然从某种意义上说,我们的过程本质上是相同的,只是我们对内部生成器的定义略有不同。) - JAB
@jamylak:谢谢。我也很想看看[]的性能表现。 - ninjagecko
@jamylak:把它看作是def fg(x): yield f(x); yield g(x); - JAB
3
第三个答案已经消失了,它长这样:[y for x in range(n) for y in (f(x), g(x))]。但这种方法可能比较慢。@jamylak 如果你愿意的话,也可以测试一下。 - Hashmush
显示剩余15条评论

18
sum( ([f(x),g(x)] for x in range(n)), [] )

这相当于[f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...

你也可以将其理解为:

def flatten(list):
    ...

flatten( [f(x),g(x)] for x in ... )

注意:正确的方法是使用 itertools.chain.from_iterable 或双重列表推导。这样可以避免在每个 + 操作上重新创建列表,从而获得 O(N) 的性能而非 O(N^2) 的性能。当我需要快速的一行代码或者时间紧迫,或者要结合的项数有界时(例如小于等于10),我仍然会使用 sum(..., [])。这就是为什么我在这里提到它,并带有该警告。你还可以使用元组:((f(x),g(x)) for ...), ()(或根据 khachik 的评论,使用生成器 fg(x) 生成两个元素的元组)。


@ArashThr:它正在执行 [f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ... - ninjagecko
注意:这个程序的运行时间复杂度为O(N^2),因此在处理大型列表时可能会很慢。 - jamylak
1
@jamylak:是的,我在评论中也提到了这一点。=) - ninjagecko
@SvenMarnach:我认为问题在于Python使用"+"来连接列表,这与数学上的使用方式不同,因为它不是可交换的。只要Python仍然使用这个"+",在语言的上下文中,我不认为这是一个反模式,尽管我会认为这是语言设计上的不良选择。好吧,在你有一个空列表的情况下,你必须指定"[]",所以这很丑陋。 - ninjagecko
在我的看法中,sum() 函数对于列表的使用是一种反模式,这是由于其灾难性能以及正确解决方案更为简洁所导致的。 - Sven Marnach
显示剩余3条评论

5

我知道OP正在寻找列表推导式的解决方案,但我想提供一种使用list.extend()的替代方法。

f = lambda x: x
g = lambda x: 10*x

result = []
extend = result.extend
for x in range(5):
    extend((f(x),g(x)))

使用单个列表推导式略微比使用双重列表推导式更快。

nums = range(100000)

def double_comprehension():
    return [func(x) for x in nums for func in (f,g)]

def list_extend():
    result = []
    extend = result.extend
    for x in nums:
        extend((f(x),g(x)))
    return result

%timeit -n100 double_comprehension()
23.4 ms ± 67 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit -n100 list_extend()
20.5 ms ± 213 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Python版本:3.8.0


1
这是一个很好的例子,其中列表推导并不总是比简单地扩展列表更好。 - undefined

2

这个 Lambda 函数将两个列表合并为一个:

zipped = lambda L1, L2: [L[i] 
                         for i in range(min(len(L1), len(L2))) 
                         for L in (L1, L2)]

例子:

>>> f = [x for x in range(5)]
>>> g = [x*10 for x in range(5)]
>>> zipped(f, g)
[0, 0, 1, 10, 2, 20, 3, 30, 4, 40]

0

在合理的情况下,您应该优先使用itertools.product()而不是在列表推导式中使用两个for子句。一些风格指南,例如Google Python Style Guide禁止在列表推导式中使用多个for子句。虽然您可能正在使用一个没有明确禁止这种做法的风格指南,但您仍然可以通过执行以下操作使推导更加简洁:

from itertools import product

result = [y(x) for y, x in product(range(n), (f, g))]

0
使用reduce的解决方案:
from functools import reduce

f    = lambda x: f"f({x})" ## Just for example
g    = lambda x: f"g({x})"
data = [1, 2, 3]

reduce(lambda acc, x: acc + [f(x), g(x)], data, [])
# => ['f(1)', 'g(1)', 'f(2)', 'g(2)', 'f(3)', 'g(3)']

这是一种解决问题的功能性方法。列表推导式本质上是另一种处理数据的方式,但在像这样的情况下,其中输入和输出之间的映射不是一对一关系时,reduce允许以某种方式生成输出。
通常,任何形式为for的实现:
result = []
for n in some_data:
  result += some_operation()
  ## etc.

例如,对于意图在列表或类似数据结构上产生副作用的for循环,可以使用累加器模式

可以重构为声明性的map/reduce/filter实现。


-2

天啊!为什么要用那么多Lambda、Flatten、Zip和Sum?这不是最简单易读的方法吗:

>>> [v
...  for x in range(5)
...      for v in (2 * x,
...                2 * x + 1)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

(将最后两个表达式替换为f(x)g(x),或其他任何表达式。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接