【问题标题】:How to turn elements of a list into their negative counterpart if their index mod 4 is greater than 1?如果它们的索引 mod 4 大于 1,如何将列表的元素转换为负数?
【发布时间】:2022-08-14 01:18:54
【问题描述】:

我正在尝试将一个列表(例如 L = [1, 2, 3, 4, 5, 6, 7, 8, ... , n])转换为 Python 中的另一个列表 L\' = [1, 2, -3, -4, 5, 6, -7, -8, ...., ±n]。 我的问题是,是否有比使用 for 循环更短/更有效的方法:

for i in range(len(L)):
    if i%4 > 1:
        L[i] *= -1

例如通过切片。

  • 列表理解可能L = [-x if i%4 > 1 else x for x in L]
  • 更短,是的。更有效率,没有。
  • 让数据与指数相差一个也许不是最明显的例子?在我注意到之前,我又做了两个小测验。
  • 您的示例和代码不匹配!请确认并澄清......如果可以的话。
  • @DanielHao 这就是我的想法,直到我注意到索引和数据偏离了一个 - 一个不好的例子。

标签: python slice


【解决方案1】:

for 循环相当高效的,因为它不会为新的L' 浪费空间,除非您当然需要保留原始列表,在这种情况下您的代码是错误的。

如果你只关心速度而不是效率,你可以从一个 numpy 数组而不是一个列表开始,因为你可以想出一个比列表操作执行得更快的操作。

如果你在乎更短,@wkl 提供的理解是要走的路。

l_prime = [-x if i%4 > 1 else x for i, x in enumerate(l)]

以下是使用timeit(标准库)计时的实现:

from timeit import timeit
import numpy as np
import itertools, operator


def samuel(l):
    for i in range(len(l)):
        if i % 4 > 1:
            l[i] *= -1
    return l


def chai(l):
    return list(map(operator.mul, l, itertools.cycle([1, 1, -1, -1])))


def wkl(l):
    return [-x if i % 4 > 1 else x for i, x in enumerate(l)]


def vladimir(l):
    ar = np.array(l)
    ar[2::4] *= -1
    ar[3::4] *= -1
    return ar.tolist()


# ensure all outcomes are the same
assert samuel(list(range(1000))) == chai(list(range(1000))) == wkl(list(range(1000))) == vladimir(list(range(1000)))

print('samuel: ', timeit(lambda: samuel(list(range(1000))), number=100000))
print('chai: ', timeit(lambda: chai(list(range(1000))), number=100000))
print('wkl: ', timeit(lambda: wkl(list(range(1000))), number=100000))
print('vladimir: ', timeit(lambda: vladimir(list(range(100000))), number=1000))

结果:

samuel:  6.736065300000519
chai:  3.7625152999999045
wkl:  7.069251500000064
vladimir:  6.424349999997503

numpy 解决方案将变得更快,无需列表转换,如下所述:

def vladimir_a(ar):
    ar[2::4] *= -1
    ar[3::4] *= -1
    return ar.tolist()


ar = np.array(list(range(1000)))
print('vladimir array: ', timeit(lambda: vladimir_a(ar), number=100000))

结果:

vladimir array:  1.269356699999662

(我知道ar会被修改100,000次,但不影响性能)

编辑:实际上,这是不公平的 - 范围都在定时部分,这将是公平的(而且不是那么好)

def vladimir_a(ar):
    ar[2::4] *= -1
    ar[3::4] *= -1
    return ar.tolist()


print('vladimir array: ', timeit(lambda: vladimir_a(np.array(range(1000))), number=100000))

结果:

vladimir array:  6.5144264999998995

所以你可能需要在你的实际用例中做一些时间来找到那里最快的。构建相同的数组 100000 次(或相同的列表)显然不是您正在做的事情,并且人们希望您在大型数据集上进行操作以提高速度甚至是一个考虑因素。

【讨论】:

  • 我建议该函数将n 作为参数,使用np.arange 创建一个数组,对其进行修改,然后返回ar。这样,所有在 numpy 数组和列表之间转换的开销都被消除了。
  • 在我的机器上,这个函数的运行速度比其他方法快 70 倍
【解决方案2】:

在 Python 3 中,您可以使用 itertoolscycle 一遍又一遍地重复一个可迭代对象,然后使用 map 将两个可迭代对象与某些函数结合起来:

>>> import itertools, operator
>>> list(map(operator.mul, range(10), itertools.cycle([1, 1, -1, -1])))
[0, 1, -2, -3, 4, 5, -6, -7, 8, 9]
>>> list(map(operator.mul, [2, 3, 5, 7, 11, 13, 17, 19], itertools.cycle([1, 1, -1, -1])))
[2, 3, -5, -7, 11, 13, -17, -19]

【讨论】:

    【解决方案3】:

    以下是各种方法的比较:

    import time
    import numpy as np
    
    n = int(1e7)
    
    s1 = time.time()
    L1 = list(range(1, n))
    for i in range(len(L1)):
        if i % 4 > 1:
            L1[i] *= -1
    e1 = time.time()
    
    s2 = time.time()
    L2 = list(range(1, n))
    L2 = np.array(L2)
    L2[np.arange(n - 1) % 4 > 1] *= -1
    L2 = L2.tolist()
    e2 = time.time()
    
    s3 = time.time()
    L3 = list(range(1, n))
    L3 = np.array(L3)
    k = (n - 1) // 4
    pat = [False, False, True, True]
    idx = np.concatenate((np.tile(pat, k), pat[:n - k * 4 - 1])).astype(bool)
    L3[idx] *= -1
    L3 = L3.tolist()
    e3 = time.time()
    
    s4 = time.time()
    L4 = list(range(1, n))
    L4 = np.array(L4)
    L4[2::4] *= -1
    L4[3::4] *= -1
    L4 = L4.tolist()
    e4 = time.time()
    
    assert all(np.array(L1) == np.array(L2))
    assert all(np.array(L1) == np.array(L3))
    assert all(np.array(L1) == np.array(L4))
    
    print(f'time1: {e1 - s1:.2f}s, time2: {e2 - s2:.2f}s, time3: {e3 - s3:.2f}s, time4: {e4 - s4:.2f}s')
    

    印刷

    time1: 3.76s, time2: 1.72s, time3: 1.58s, time4: 1.52s
    

    【讨论】:

    • 向@Vladimir Fokow 的切片方法大喊大叫
    • 考虑到问题的重点是速度和效率,给它们计时是个好主意,但是你会引入一些开销,并且使用 time.time() 的计时并不是非常可靠 - 看看我给出的关于如何使用非常可靠和合适的答案-for-purpose timeit Python 模块
    • @Grismar 感谢您的提示。什么是开销?
    • 与我的 Vladimir 解决方案版本相同,这对 numpy 解决方案不公平,不需要列表转换。 (尽管您可以争辩说显示差异是有道理的,我也包括在内)
    【解决方案4】:

    为了补充@Grismar 的答案,numpy 数组可以比迭代快得多,因为它们并行执行操作(甚至在 CPU 上)。

    以下是如何使用 numpy 数组(使用切片)解决您的问题:

    import numpy as np
    
    # If you already have a list `L`:
    ar = np.array(L)
    
    # But it’s faster to use numpy to 
    # generate the array of numbers 
    # (here: integers from 1 to n):
    n = 15
    ar = np.arange(1, n + 1)
    
    ar[2::4] *= -1
    ar[3::4] *= -1
    
    # You can convert a numpy array into a list. 
    # Note that this step may not be necessary 
    # at all, because numpy arrays can be
    # used where you would use lists. 
    # But for completeness, here is how to do it:
    L_prime = ar.tolist()
    

    您可以使用的功能:

    def make_array(n):
        ar = np.arange(1, n + 1)
        ar[2::4] *= -1
        ar[3::4] *= -1
        return ar
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-12
      • 2014-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多