【问题标题】:Concatenate tuples using sum()使用 sum() 连接元组
【发布时间】:2017-06-22 21:56:42
【问题描述】:

this post我了解到您可以将元组与sum()连接起来:

>>> tuples = (('hello',), ('these', 'are'), ('my', 'tuples!'))
>>> sum(tuples, ())
('hello', 'these', 'are', 'my', 'tuples!')

这看起来很不错。但为什么这行得通?而且,这是最优的,还是 itertools 的某些东西比这个结构更可取?

【问题讨论】:

  • 为什么它不能工作?它只是将元组添加在一起,但并不是特别有效。看看itertools.chain。例如,tuple(chain(*tuples))
  • @PM2Ring。避免像这样使用chain,因为它比sum 效率更低(除非元组的集合非常小)。请改用chain.from_iterable
  • @ekhumoro 哎呀!是的,chain.from_iterable 更好。正如 Boud 的回答所示,对于小的元组集合,它实际上比 sum 慢。

标签: python sum tuples itertools


【解决方案1】:

python 中的加法运算符连接元组:

('a', 'b')+('c', 'd')
Out[34]: ('a', 'b', 'c', 'd')

来自sum的文档字符串:

返回一个“开始”值(默认值:0)加上一个可迭代的 数字

这意味着sum 不是从可迭代的第一个元素开始,而是从通过start= 参数传递的初始值开始。

默认情况下sum 与数字一起使用,因此默认起始值​​为0。因此,对一个可迭代的元组求和需要从一个空元组开始。 () 是一个空元组:

type(())
Out[36]: tuple

因此工作串联。

根据性能,这里是一个比较:

%timeit sum(tuples, ())
The slowest run took 9.40 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 285 ns per loop


%timeit tuple(it.chain.from_iterable(tuples))
The slowest run took 5.00 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 625 ns per loop

现在 t2 的大小为 10000:

%timeit sum(t2, ())
10 loops, best of 3: 188 ms per loop

%timeit tuple(it.chain.from_iterable(t2))
1000 loops, best of 3: 526 µs per loop

因此,如果您的元组列表很小,您不必费心。如果是中等或更大的,你应该使用itertools

【讨论】:

  • 有趣的时间。你用的是哪个 Python 版本?
  • @PM2Ring 3.5 64bits
  • best of 3 => 请参考 ipython 中的 %timeit 文档
【解决方案2】:

这很聪明,我不得不笑,因为 help 明确禁止字符串,这也是不可变的,但它有效

sum(...)
    sum(iterable[, start]) -> value
    
    Return the sum of an iterable of numbers (NOT strings) plus the value
    of parameter 'start' (which defaults to 0).  When the iterable is
    empty, return start.

您可以添加元组以获得更大的新元组。而且由于您提供了一个元组作为起始值,因此加法有效。

【讨论】:

  • 在此示例中,sum 没有对字符串求和:在此处连接的输入中没有两个单独的字符串。 (例如,无法使用 sumhelloworld 转换为 helloworld。)
  • IMO Python 所做的只是愚蠢的。 Sum 应该能够对支持 + 运算符的任何内容求和。字符串可以。以性能和良好约定的名义明确禁止字符串的特殊情况(虽然 python 有很多其他不被禁止的反模式)只是不好的设计
  • @ShreevatsaR 我很清楚这一点。帮助中提到了字符串,但我接着说这确实是在添加元组。我只是觉得这很有趣,并假设人们可以阅读。
  • @progo - 我不确定它为什么被禁止,但我同意, sum 应该做 plus 做的事情。也许它是为了捕捉字符串被误认为整数的常见错误。但是还是……
  • “帮助明确禁止字符串,但它有效” 这可能被误解为“它也适用于字符串”,这是不正确的。另外,这如何回答这个问题?引用的帮助甚至没有提到值或开始可以是数字以外的任何其他内容,更不用说元组了。
【解决方案3】:

只是用更多基准来补充公认的答案:

import functools, operator, itertools
import numpy as np
N = 10000
M = 2

ll = tuple(tuple(x) for x in np.random.random((N, M)).tolist())

%timeit functools.reduce(operator.add, ll)
# 407 ms ± 5.63 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit functools.reduce(lambda x, y: x + y, ll)
# 425 ms ± 7.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit sum(ll, ())
# 426 ms ± 14.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit tuple(itertools.chain(*ll))
# 601 µs ± 5.43 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit tuple(itertools.chain.from_iterable(ll))
# 546 µs ± 25.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

EDIT:代码已更新为实际使用元组。而且,根据 cmets,最后两个选项现在位于 tuple() 构造函数中,并且所有时间都已更新(为了保持一致性)。 itertools.chain* 选项仍然是最快的,但现在边距减少了。

【讨论】:

  • 您的最后两次计时不具有代表性。 itertools.chainitertools.chain.from_iterable 返回迭代器。对于公平的时间安排,您需要使用 tuple(itertools.chain...) 迭代这些。
【解决方案4】:

之所以有效,是因为加法被重载(在元组上)以返回连接的元组:

>>> () + ('hello',) + ('these', 'are') + ('my', 'tuples!')
('hello', 'these', 'are', 'my', 'tuples!')

这基本上就是sum 正在做的事情,你给一个空元组的初始值,然后将元组添加到它。

但是这通常是一个坏主意,因为添加元组会创建一个新元组,因此您创建几个中间元组只是为了将它们复制到连接的元组中:

()
('hello',)
('hello', 'these', 'are')
('hello', 'these', 'are', 'my', 'tuples!')

这是一个具有二次运行时行为的实现。通过避免中间元组可以避免这种二次运行时行为。

>>> tuples = (('hello',), ('these', 'are'), ('my', 'tuples!'))

使用嵌套生成器表达式:

>>> tuple(tuple_item for tup in tuples for tuple_item in tup)
('hello', 'these', 'are', 'my', 'tuples!')

或者使用生成器函数:

def flatten(it):
    for seq in it:
        for item in seq:
            yield item


>>> tuple(flatten(tuples))
('hello', 'these', 'are', 'my', 'tuples!')

或者使用itertools.chain.from_iterable:

>>> import itertools
>>> tuple(itertools.chain.from_iterable(tuples))
('hello', 'these', 'are', 'my', 'tuples!')

如果您对它们的性能感兴趣(使用我的simple_benchmark package):

import itertools
import simple_benchmark

def flatten(it):
    for seq in it:
        for item in seq:
            yield item

def sum_approach(tuples):
    return sum(tuples, ())

def generator_expression_approach(tuples):
    return tuple(tuple_item for tup in tuples for tuple_item in tup)

def generator_function_approach(tuples):
    return tuple(flatten(tuples))

def itertools_approach(tuples):
    return tuple(itertools.chain.from_iterable(tuples))

funcs = [sum_approach, generator_expression_approach, generator_function_approach, itertools_approach]
arguments = {(2**i): tuple((1,) for i in range(1, 2**i)) for i in range(1, 13)}
b = simple_benchmark.benchmark(funcs, arguments, argument_name='number of tuples to concatenate')

b.plot()

(Python 3.7.2 64 位,Windows 10 64 位)

因此,虽然sum 方法在您仅连接几个元组时非常快,但如果您尝试连接大量元组,则会非常慢。许多元组的测试方法中最快的是itertools.chain.from_iterable

【讨论】:

    【解决方案5】:

    第二个参数start,你放()的地方,是要添加到的起始对象,默认为0用于数字相加。

    这是sum 的示例实现(我所期望的):

    def sum(iterable, /, start=0):
        for element in iterable:
            start += element
        return start
    

    例子:

    >>> sum([1, 2, 3])
    6
    >>> tuples = (('hello',), ('these', 'are'), ('my', 'tuples!'))
    >>> sum(tuples)
    TypeError: unsupported operand type(s) for +=: 'int' and 'tuple'
    >>> sum(tuples, ())
    ('hello', 'these', 'are', 'my', 'tuples!')
    >>> 
    

    因为支持与+ 的元组连接,所以它可以工作。

    实际上这被翻译成:

    >>> () + ('hello',) + ('these', 'are') + ('my', 'tuples!')
    ('hello', 'these', 'are', 'my', 'tuples!')
    >>> 
    

    【讨论】:

    • 您好,我看到您对 pandas 和数据科学非常了解。我找不到私下联系你的方法。我正在尝试尽可能多地建立联系,并在我前进的过程中向编码人员学习。如果很酷就一些问题亲自向您发送消息,请告诉我
    • @georgehere 你可以查看我的电子邮件 :)
    猜你喜欢
    • 2017-04-26
    • 1970-01-01
    • 2020-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-15
    • 2021-06-01
    相关资源
    最近更新 更多