在python中,生成元组的最快方法是什么:(1.0,0.0,0.0,2.0,0.0,0.0,...,N,0.0,0.0)?

问题描述 投票:0回答:3

寻找使用标题中提到的模式生成元组的最快方法,即:

(1.0, 0.0, 0.0, 2.0, 0.0, 0.0, ..., N, 0.0, 0.0)

任何积极的N尊重:round(N) == N

python tuples
3个回答
3
投票

最快我能想出的是使用itertools函数将所有工作推送到C层:

from itertools import chain, repeat

def make_tuple(N):
    return return tuple(chain.from_iterable(zip(map(float, range(1, round(N)+1)), repeat(0.0), repeat(0.0))))

repeat使零,map(float, range(1, round(N)+1))使非零值,ziping他们在一起使三tuples chain.from_iterable变平,所以tuple直接构造最终结果。

虽然它确实涉及临时三tuples(不像Patrick's answer),在CPython参考解释器上它实际上并没有创建新的tuples;如果在请求下一个值时没有其他对zip的引用(并且tuple每次都释放它的引用),则tuple被优化为在最后结果中重用chain.from_iterable用于新结果。

为了与其他答案进行比较,对于ipythonN微基准测试为150:

>>> %timeit -r5 make_tuple(150)
28.1 µs ± 1.67 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)
>>> %timeit -r5 make_tuple_tim_peters(150)
17.1 µs ± 52 ns per loop (mean ± std. dev. of 5 runs, 100000 loops each)
>>> %timeit -r5 make_tuple_julien(150)
154 µs ± 1.85 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)
>>> %timeit -r5 tuple(values_patrick_haugh(150))  # Modified to convert to float properly
40.7 µs ± 1.29 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)

我尝试了一些类似于我自己的方法的其他方法,我自己使用listcomps和genexprs,但是它们都没有达到40μs以下,所以我没有费心去发布它们。

Tim Peter's solution绝对是迄今为止发布最快的,并且不太可能被打败。作为he notes,它需要更多的内存,因为在高峰内存使用时,它需要存储整个结果tuple和临时list(虽然每个应该是精确大小,没有过度分配),这意味着容器的峰值内存大约是两倍什么是“需要”。我确实需要tuple来进行全面定位(因为它不知道结果有多大),在当前的CPython中,作为实现细节,意味着大约25%的位置。节约,但不是重要的节约;如果表现很重要,我几乎总是选择蒂姆的解决方案。

后来的更新:我最终找到了能够找到Tim的答案的东西,但只能借助于numpy,并且渐进式改进非常简单:

from numpy import arange, zeros

def make_tuple_numpy(N):
    ret = zeros(3*round(N))
    ret[::3] = arange(1., N+1.)
    return tuple(ret.tolist())

它与Tim的答案基本相同,它只是使用numpy来批量处理原始C基元类型(例如,np.arange直接生成浮点形式的范围,而不创建一堆Python ints只将它们转换为floats),使用tolist方法让numpy执行转换到list而没有Python迭代器参与,然后包装在tuple构造函数(特殊情况下list,所以再次没有迭代器参与)。尽管如此,优势仍然微不足道:

>>> %timeit -r5 make_tuple_numpy(150)
13.8 µs ± 158 ns per loop (mean ± std. dev. of 5 runs, 100000 loops each)

与Tim的解决方案相比,它的运行时间进一步缩短了约20%,但除非你这么做,否则导入numpy的成本可能会节省成本。


5
投票

谁知道? ;-)在CPython中,“技巧”通常涉及避免显式的Python级循环,并避免二次时间链接。这是一种方式:

def gentup(N):
    NI = round(N)
    assert N == NI
    result = [0.] * (3 * NI)
    result[::3] = map(float, range(1, NI + 1))
    return tuple(result)

然后,例如,

>>> gentup(4)
(1.0, 0.0, 0.0, 2.0, 0.0, 0.0, 3.0, 0.0, 0.0, 4.0, 0.0, 0.0)

然后,所有真正的工作都以“C速度”运行,甚至float只被查询一次(尽管被调用round(N)次)。


2
投票

这是一种不会生成任何临时元组的方法。

def values(N):
    nums = range(1, N+1)
    for n in nums:
        yield n
        yield 0
        yield 0

print(tuple(values(5)))
# (1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0)
© www.soinside.com 2019 - 2024. All rights reserved.