在 Python 中生成数字列表及其负数

upe*_*upe 60 python python-3.x

是否有一个方便的单行程序来生成 Python 中的数字列表及其负对应项?

例如,假设我想生成一个包含数字 6 到 9 和 -6 到 -9 的列表。

我目前的做法是:

l = [x for x in range(6,10)]
l += [-x for x in l]
Run Code Online (Sandbox Code Playgroud)

一个简单的“单行”将是:

l = [x for x in range(6,10)] + [y for y in range(-9, -5)]
Run Code Online (Sandbox Code Playgroud)

但是,生成两个列表然后将它们连接在一起似乎很不方便。

Dat*_*ice 68

我不确定顺序是否重要,但您可以创建一个元组并将其解压缩到列表理解中。

nums = [y for x in range(6,10) for y in (x,-x)]
print(nums)
[6, -6, 7, -7, 8, -8, 9, -9]
Run Code Online (Sandbox Code Playgroud)


Der*_*nik 64

创建一个漂亮且可读的函数:

def range_with_negatives(start, end):
    for x in range(start, end):
        yield x
        yield -x
Run Code Online (Sandbox Code Playgroud)

用法:

list(range_with_negatives(6, 10))
Run Code Online (Sandbox Code Playgroud)

这就是您为任何东西获得方便的单衬纸的方式。避免试图看起来像一个神奇的职业黑客。

  • 对列表/字典理解的偏见在 SO 上很常见,并且通常通过说它们不“可读”来证明是合理的。构造的固有可读性(而不是“如何”使用它)在很大程度上是主观的。就我个人而言,我发现理解*更*可读,而这个解决方案则要差得多(比较:“写下每个 50 岁以上男性的血压”与“检查每个人。好吧,他们是男性吗?如果是的话,那么.. .”)。我使用它们是因为这个原因,而不是因为我想“看起来像”任何东西。如果这是问题所在,那么长的理解可以被分解为多行。 (29认同)
  • 很公平。但这就是主观性的来源:我个人*确实*发现列表比较解决方案在精神紧张方面至少与生成器函数一样可读。然而,我会通过将“y”重命名为“signedValue”来提高 Datanovice 答案的可读性(或者更确切地说,对我而言的可读性)。对我来说,定义函数方法的真正附加值是按照问题要求的严格顺序提供结果(先是正数,然后是负数),同时避免 Barmar 的“链式”单行句问题,其中略有不同数字参数必须被硬编码两次。 (3认同)
  • 我同意关于列表推导式的普遍观点,但是定义这样的函数也是一种非常有用的技术。(通过编写递归生成器并将顶级结果传递给“列表”或其他任何东西,从递归算法中收集结果尤其强大。)经过多年尝试编写如何最好地做出这些决策,唯一的通用原则是对我来说:如果有一个明显的、好的名字可以给程序中的某些东西起一个名字,那就抓住机会这样做(函数是我们这样做的方法之一)。 (3认同)

Roa*_*WMC 59

我想说最简单的解决方案是使用*解包运算符将两个范围解包到一个列表中:

>>> [*range(6, 10), *range(-9, -5)]
[6, 7, 8, 9, -9, -8, -7, -6]
Run Code Online (Sandbox Code Playgroud)

这不仅是迄今为止提出的最短答案,而且也是性能最高的答案,因为它只构造一个列表,并且不涉及超出两个ranges 的函数调用。

我通过使用timeit模块测试所有这个问题的答案来验证这一点:

答案 ID 方法 timeit 结果
-------------------------------------------------- ------------------------------------------------
(有问题)[x for x in range(6,10)] + [y for y in range(-9, -5)] 每个循环 0.843 usec
(this answer) [*range(6, 10), *range(-9, -5)] 每个循环 0.509 usec
61348876 [y for x in range(6,10) for y in (x,-x)] 每个循环 0.754 微秒
61349149 list(range_with_negatives(6, 10)) 每个循环 0.795 微秒
61348914 list(itertools.chain(range(6, 10), range(-9, -5))) 每个循环 0.709 微秒
61366995 [sign*x 表示符号,x in itertools.product((-1, 1), range(6, 10))] 每个循环 0.899 微秒
61371302 list(range(6, 10)) + list(range(-9, -5)) 每个循环 0.729 微秒
61367180 list(range_with_negs(6, 10)) 每个循环 1.95 微秒

(在我自己的计算机上使用 Python 3.6.9 执行的 timeit 测试(平均规格))

  • 当您拥有的只是一个包含 <10 个项目的示例时,我不太热衷于假设性能是相关的,但这显然是最简单的解决方案。 (4认同)
  • @aldokkani`[*范围(x,y),*范围(-y + 1,-x + 1)]` (3认同)
  • 如何在不对其进行硬编码的情况下找出负值的开始和结束? (2认同)

Bar*_*mar 25

You can use itertools.chain() to concatenate the two ranges.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Run Code Online (Sandbox Code Playgroud)

  • 我会使用 'range(-6, -10, -1)' 来明确顺序并不重要(并用变量替换 6 和 10) (3认同)

Fra*_*Vel 10

您可以使用itertools.product,这是笛卡尔积。

[sign*x for sign, x in product((-1, 1), range(6, 10))]
[-6, -7, -8, -9, 6, 7, 8, 9]
Run Code Online (Sandbox Code Playgroud)

由于您使用乘法,这可能会更慢,但应该易于阅读。

如果您想要一个纯功能的解决方案,您还可以导入itertools.starmapoperator.mul

from itertools import product, starmap
from operator import mul

list(starmap(mul, product((-1, 1), range(6, 10))))
Run Code Online (Sandbox Code Playgroud)

但是,这不太可读。

  • 我发现与嵌套列表推导式相比,“product”、“starmap”和“opertaor.mul”的使用不必要地迟钝,但我同意使用乘法的建议。`[x * sign for sign in (1, -1) for x in range(6, 10)]` 仅比 `[y for x in range(6, 10) for y in (x, -x)]`,在顺序很重要的情况下,比基于元组的排序方法快 3 倍以上。 (8认同)

Gre*_*ard 10

你真的很接近,结合了两个range对象。但是有一种更简单的方法来做到这一点:

>>> list(range(6, 10)) + list(range(-9, -5))
[6, 7, 8, 9, -9, -8, -7, -6]
Run Code Online (Sandbox Code Playgroud)

即,将每个range对象转换为一个列表,然后将两个列表连接起来。

另一种方法,使用 itertools:

>>> list(itertools.chain(range(6, 10), range(-9, -5)))
[6, 7, 8, 9, -9, -8, -7, -6]
Run Code Online (Sandbox Code Playgroud)

itertools.chain()就像一个广义的+:它不是添加两个列表,而是一个接一个地链接一个迭代器以创建一个“超级迭代器”。然后将它传递给list()你,你会得到一个具体的列表,内存中包含你想要的所有数字。

  • 这个答案很有价值,因为我们认识到“[x for x in ...]”更好地拼写为“list(...)”。 (4认同)

hBy*_*2Py 7

IMO 在itertools.chain其他几个答案中使用的方法绝对是迄今为止提供的方法中最干净的。

但是,由于在您的情况下,值的顺序无关紧要,因此您可以避免定义两个显式range对象,从而避免执行负range索引所需的所有逐一数学运算,方法是使用itertools.chain.from_iterable

>>> import itertools
>>> list(itertools.chain.from_iterable((x, -x) for x in range(6, 10)))
[6, -6, 7, -7, 8, -8, 9, -9]
Run Code Online (Sandbox Code Playgroud)

有点冗长,但足够可读。

另一个类似的选择是使用带有 plain 的元组/参数解包chain

>>> list(itertools.chain(*((x, -x) for x in range(6, 10))))
[6, -6, 7, -7, 8, -8, 9, -9]
Run Code Online (Sandbox Code Playgroud)

更简洁,但我发现元组在快速扫描中更难理解。


Kyl*_*leL 5

权衡另一种可能性。

如果你想要可读性,你原来的单行代码非常好,但我会改变范围,因为我认为负边界会让事情变得不那么清楚。

[x for x in range(6, 10)] + [-x for x in range(6, 10)]
Run Code Online (Sandbox Code Playgroud)


PiC*_*CTo 5

这是一个主题的变体(参见@Derte Trdelnik回答),遵循itertoolswhere的哲学

迭代器构建块 [...] 可以单独使用或组合使用。

这个想法是,当我们定义一个新函数时,我们不妨让它通用:

def interleaved_negatives(it):
    for i in it:
        yield i
        yield -i
Run Code Online (Sandbox Code Playgroud)

并将其应用于特定的range迭代器:

list(interleaved_negatives(range(6, 10)))
Run Code Online (Sandbox Code Playgroud)