web-dev-qa-db-ja.com

sum()を使用してタプルを連結します

から この投稿 タプルを次のものと連結できることを学びました:

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

これはかなりいいですね。しかし、なぜこれが機能するのですか?そして、これは最適ですか、それともこの構成よりも好ましいitertoolsからの何かがありますか?

21
Stephen Rauch

加算演算子は、Pythonでタプルを連結します。

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

sumのdocstringから:

'開始'値(デフォルト: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

サイズが10000のt2の場合:

%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を使用する必要があります。

24
Boud

それは賢いです、そして助けが文字列を明示的に禁止しているので私は笑わなければなりませんでした、しかしそれはうまくいきます

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.

タプルを追加して、新しい、より大きなタプルを取得できます。また、開始値としてタプルを指定したため、加算は機能します。

4
tdelaney

受け入れられた回答をさらにいくつかのベンチマークで補完するためだけに:

_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 [〜#〜]:コードは実際にタプルを使用するように更新されます。そして、コメントによると、最後の2つのオプションはTuple()コンストラクター内にあり、(一貫性を保つために)常に更新されています。 _itertools.chain*_オプションは依然として最速ですが、マージンが減少しています。

1
norok2

連結されたタプルを返すために(タプルで)加算がオーバーロードされるため、これは機能します。

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

これは基本的にsumが行っていることです。空のタプルの初期値を指定してから、それにタプルを追加します。

ただし、タプルを追加すると新しいタプルが作成されるため、これは一般的に悪い考えです。したがって、連結されたタプルにコピーするためだけに、いくつかの中間タプルを作成します。

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

これは、2次の実行時動作を持つ実装です。この2次ランタイム動作は、中間タプルを回避することで回避できます。

>>> 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!')

そして、これらがどのように機能するかに興味がある場合は(my simple_benchmarkパッケージ ):

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()

enter image description here

(Python 3.7.2 64ビット、Windows 10 64ビット)

したがって、sumアプローチは、少数のタプルのみを連結する場合は非常に高速ですが、多数のタプルを連結しようとすると非常に遅くなります。多くのタプルに対してテストされたアプローチの中で最も速いのは itertools.chain.from_iterable

0
MSeifert