【问题标题】:Generate list of numbers and their negative counterparts in Python在 Python 中生成数字列表及其负数
【发布时间】:2020-06-20 08:11:40
【问题描述】:

在 Python 中是否有一种方便的单行代码来生成数字列表及其负数?

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

我目前的做法是:

l = [x for x in range(6,10)]
l += [-x for x in l]

一个简单的“单行”是:

l = [x for x in range(6,10)] + [y for y in range(-9, -5)]

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

【问题讨论】:

  • 正数应该在负数之前吗?
  • @Erich 不,在我的情况下顺序无关紧要。
  • 如果顺序无关紧要,它甚至需要是一个列表吗?一个集合是好的(无序的),还是一个生成器或元组(两者都是有序的)?
  • @J.G.稍后我可能想创建一个图形。据说使用scatter 等。所以任何scalar or array-like, shape 都可以。
  • 这些答案大多读起来像一些反高尔夫解决方案;排序、生成器函数、迭代工具。我宁愿维护您提供的代码,也不愿维护大多数“答案”。

标签: python python-3.x


【解决方案1】:

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

nums = [y for x in range(6,10) for y in (x,-x)]
print(nums)
[6, -6, 7, -7, 8, -8, 9, -9]

【讨论】:

    【解决方案2】:

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

    def range_with_negatives(start, end):
        for x in range(start, end):
            yield x
            yield -x
    

    用法:

    list(range_with_negatives(6, 10))
    

    这就是你如何获得一个方便的单线的任何东西。避免试图看起来像一个神奇的职业黑客。

    【讨论】:

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

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

    >>> [*range(6, 10), *range(-9, -5)]
    [6, 7, 8, 9, -9, -8, -7, -6]
    

    这不仅是目前提出的最短答案,也是性能最高的答案,因为它只构建一个列表,并且不涉及两个 ranges 之外的函数调用。

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

    答案 ID 方法 timeit 结果 -------------------------------------------------- ------------------------------------------------ (有问题)[x for x in range(6,10)] + [y for y in range(-9, -5)] 每个循环 0.843 微秒 (这个答案) [*range(6, 10), *range(-9, -5)] 每个循环 0.509 微秒 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 for sign, 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 测试(平均规格))

    【讨论】:

    • 当你只有
    • 如何在不硬编码的情况下找出负值的开始和结束?
    • @aldokkani [*range(x, y), *range(-y + 1, -x + 1)]
    • @RoadrunnerWMC 我建议[*range(x, y), *range(-x, -y, -1)] 以获得更好的可读性。
    • 我建议将其放入答案中,因为现在它太具体且“硬编码”-y
    【解决方案4】:

    您可以使用itertools.chain() 连接两个范围。

    import itertools
    list(itertools.chain(range(6, 10), range(-9, -5)))
    

    【讨论】:

    • 我会使用'range(-6, -10, -1)'来清楚顺序无关紧要(并用变量替换6和10)
    【解决方案5】:

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

    [sign*x for sign, x in product((-1, 1), range(6, 10))]
    [-6, -7, -8, -9, 6, 7, 8, 9]
    

    这可能会因为您使用乘法而变慢,但应该易于阅读。

    如果你想要一个纯函数式的解决方案,你也可以导入itertools.starmapoperator.mul

    from itertools import product, starmap
    from operator import mul
    
    list(starmap(mul, product((-1, 1), range(6, 10))))
    

    但是,这不太可读。

    【讨论】:

    • 我发现productstarmapopertaor.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)] 慢 10%,在顺序很重要的情况下,比基于元组的排序方法快 3 倍以上。
    • 这是一个巧妙的想法,但可能对问题细节有点过于具体。其他人提供的技术应用更广泛。
    • @ApproachingDarknessFish 我同意应该避免使用starmapmul,但我认为product 使其更具可读性,因为它将迭代器及其元素分开分组。列表理解中的双循环也可能令人困惑,因为它们的顺序出人意料。
    【解决方案6】:

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

    >>> list(range(6, 10)) + list(range(-9, -5))
    [6, 7, 8, 9, -9, -8, -7, -6]
    

    即把每个range对象转换成一个列表,然后将两个列表串联起来。

    另一种方法,使用 itertools:

    >>> list(itertools.chain(range(6, 10), range(-9, -5)))
    [6, 7, 8, 9, -9, -8, -7, -6]
    

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

    【讨论】:

    • 这个答案很有价值,因为[x for x in ...] 拼写更好list(...)
    【解决方案7】:

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

    但是,由于在您的情况 the order of the values doesn't matter 中,您可以避免必须定义两个显式的 range 对象,从而避免使用 @987654325 执行负 range 索引所需的所有非一数学@:

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

    有点冗长,但足够可读。

    另一个类似的选项是使用纯 chain 的元组/参数解包:

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

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

    【讨论】:

      【解决方案8】:

      考虑另一种可能性。

      如果您想要可读性,您的原始单行代码非常好,但我会将范围更改为相同,因为我认为负边界会使事情变得不那么清晰。

      [x for x in range(6, 10)] + [-x for x in range(6, 10)]
      

      【讨论】:

        【解决方案9】:

        这是一个主题的变体(参见@Derte Trdelnikanswer),遵循itertools where 的理念

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

        我们的想法是,当我们定义一个新函数时,我们不妨将其设为通用:

        def interleaved_negatives(it):
            for i in it:
                yield i
                yield -i
        

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

        list(interleaved_negatives(range(6, 10)))
        

        【讨论】:

          【解决方案10】:

          可以有不同的方法来完成工作。

          给定的变量: 1.开始=6 2. 停止=10

          你也可以试试这个,不同的方法:

          def mirror_numbers(start,stop):
            if start<stop:
              val=range(start,stop)
              return [j if i < len(val) else -j for i,j in enumerate([x for x in val]*2) ]
          
          mirror_numbers(6,10)
          

          【讨论】:

            【解决方案11】:

            使用迭代工具:

            >>> list(itertools.chain(range(6, 10), range(-9, -5)))
            [6, 7, 8, 9, -9, -8, -7, -6]
            

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

            【讨论】:

            【解决方案12】:

            如果你想保持你指定的顺序,你可以使用 Python 的内置范围生成器和条件:

            def range_with_negs(start, stop):
                for value in range(-(stop-1), stop):      
                    if (value <= -start) or (value >= start):
                        yield value
            

            这会给你输出:

            In [1]: list(range_with_negs(6, 10))
            Out[1]: [-9, -8, -7, -6, 6, 7, 8, 9]
            

            还可以使用 0 作为整个范围的开始。

            【讨论】:

            • 这对于start 的大值非常低效。
            【解决方案13】:

            我喜欢对称。

            a = 6
            b = 10
            
            nums = [x+y for x in (-(a+b-1),0) for y in range(a,b)]
            

            结果是[-9, -8, -7, -6, 6, 7, 8, 9]

            我相信 nums 表达式可以改进,“in”和“range”后面的内容在我看来仍然不平衡。

            【讨论】:

              【解决方案14】:

              似乎只有两个答案真正给出了一条线,所以这里是另一个:

              [i for l in map(lambda x: (x, -x), range(6, 10)) for i in l]
              
              [6, -6, 7, -7, 8, -8, 9, -9]
              

              【讨论】:

                猜你喜欢
                • 2021-12-04
                • 2019-06-30
                • 2011-05-24
                • 1970-01-01
                • 2014-01-24
                • 1970-01-01
                • 2011-09-22
                • 1970-01-01
                • 2023-02-20
                相关资源
                最近更新 更多