De ce post j’ai appris que vous pouvez concaténer des n-uplets avec:
>>> tuples = (('hello',), ('these', 'are'), ('my', 'tuples!'))
>>> sum(tuples, ())
('hello', 'these', 'are', 'my', 'tuples!')
Ce qui semble joli Nice. Mais pourquoi ça marche? Et, est-ce optimal, ou y at-il quelque chose de itertools
qui serait préférable à cette construction?
l'opérateur d'addition concatène les n-uplets en python:
('a', 'b')+('c', 'd')
Out[34]: ('a', 'b', 'c', 'd')
De la docstring de sum
:
Retourne la somme d'une valeur 'de départ' (valeur par défaut: 0) plus une valeur itérable de Nombres
Cela signifie que sum
ne commence pas par le premier élément de votre itérable, mais par une valeur initiale transmise par l'argument start=
.
Par défaut, sum
est utilisé avec numérique. La valeur de départ par défaut est donc 0
. Donc, pour résumer une multiplicité de tuples, il faut commencer par un tuple vide. ()
est un tuple vide:
type(())
Out[36]: Tuple
Par conséquent, la concaténation de travail.
Selon la performance, voici une comparaison:
%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
Maintenant avec un t2 de taille 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
Donc, si votre liste de n-uplets est petite, cela ne vous dérange pas. Si sa taille est moyenne ou supérieure, vous devez utiliser itertools
.
C’est intelligent et j’ai dû rire car l’aide interdit expressément les ficelles, mais ça marche
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.
Vous pouvez ajouter des nuplets pour obtenir un nouveau tuple plus grand. Et puisque vous avez donné un tuple comme valeur de départ, l’ajout fonctionne.
Juste pour compléter la réponse acceptée avec quelques points de repère supplémentaires:
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: le code est mis à jour pour utiliser les n-uplets. Et, comme indiqué dans les commentaires, les deux dernières options se trouvent maintenant dans un constructeur Tuple()
et toutes les heures ont été mises à jour (pour des raisons de cohérence). Les options itertools.chain*
sont toujours les plus rapides, mais maintenant la marge est réduite.
Cela fonctionne parce que l'addition est surchargée (sur les n-uplets) pour renvoyer le tuple concaténé:
>>> () + ('hello',) + ('these', 'are') + ('my', 'tuples!')
('hello', 'these', 'are', 'my', 'tuples!')
C’est essentiellement ce que sum
fait: vous donnez la valeur initiale d’un tuple vide, puis vous ajoutez les n-uplets à cela.
Cependant, c’est généralement une mauvaise idée, car l’ajout de n-uplets crée un nouveau tuple. Vous devez donc créer plusieurs n-uplets intermédiaires pour les copier dans le tuple concaténé:
()
('hello',)
('hello', 'these', 'are')
('hello', 'these', 'are', 'my', 'tuples!')
C'est une implémentation qui a un comportement d'exécution quadratique. Ce comportement d'exécution quadratique peut être évité en évitant les n-uplets intermédiaires.
>>> tuples = (('hello',), ('these', 'are'), ('my', 'tuples!'))
Utilisation d'expressions de générateur imbriquées:
>>> Tuple(tuple_item for tup in tuples for Tuple_item in tup)
('hello', 'these', 'are', 'my', 'tuples!')
Ou en utilisant une fonction de générateur:
def flatten(it):
for seq in it:
for item in seq:
yield item
>>> Tuple(flatten(tuples))
('hello', 'these', 'are', 'my', 'tuples!')
Ou en utilisant itertools.chain.from_iterable
:
>>> import itertools
>>> Tuple(itertools.chain.from_iterable(tuples))
('hello', 'these', 'are', 'my', 'tuples!')
Et si vous êtes intéressé par leurs performances (avec mon 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 bits, Windows 10 64 bits)
Ainsi, bien que l'approche sum
soit très rapide si vous ne concatérez que quelques n-uplets, elle sera très lente si vous essayez de concaténer beaucoup de n-uplets. La plus rapide des approches testées pour de nombreux tuples est itertools.chain.from_iterable