46 votes

Besoin d'un moyen rapide de compter et de résumer un itérable en un seul passage

Quelqu'un peut-il m'aider? J'essaie de trouver une façon de calculer

>>> sum_widths = sum(col.width for col in cols if not col.hide)

et aussi de compter le nombre d'éléments dans cette somme, sans avoir à faire deux passes de plus de cols.

Il semble incroyable, mais après le balayage mst-lib (fonctions intégrées, itertools, functools, etc), je ne pouvais même pas trouver une fonction qui compte le nombre de membres dans un objet iterable. J'ai trouvé la fonction itertools.count, ce qui sonne comme ce que je veux, mais C'est vraiment juste un air faussement nommé range fonction.

Après un peu de réflexion, je suis venu avec la suivante (qui est si simple que l'absence d'une fonction de la bibliothèque peut être excusable, à l'exception de sa obtuseness):

>>> visable_col_count = sum(col is col for col in cols if not col.hide)

Cependant, l'utilisation de ces deux fonctions nécessite deux passages de l'itératif, qui vient de me frotte le mauvais sens.

Comme une alternative, la fonction suivante est ce que je veux:

>>> def count_and_sum(iter):
>>>     count = sum = 0
>>>     for item in iter:
>>>         count += 1
>>>         sum += item
>>>     return count, sum

Le problème, c'est qu'il faut 100 fois plus longtemps (selon timeit) comme la somme d'un générateur de forme d'expression.

Si quelqu'un peut venir avec un simple one-liner qui fait ce que je veux, s'il vous plaît laissez-moi savoir (à l'aide de Python 3.3).

Edit 1

Beaucoup de bonnes idées ici, les gars. Merci à tous ceux qui ont répondu. Il va me prendre un certain temps pour digérer toutes ces réponses, mais je le ferai, et je vais essayer de chercher un pour vérifier.

Edit 2

J'ai répété les horaires sur mes deux humbles suggestions (count_and_sum de la fonction et de séparer les 2 sum des fonctions) et découvert que mon timing était, probablement en raison d'une auto-sauvegarde planifiée processus en cours d'exécution en arrière-plan.

J'ai également chronométré la plupart des excellentes suggestions étant donné que des réponses ici, tous avec le même modèle. L'analyse de ces réponses a été toute une éducation pour moi: de nouvelles utilisations pour deque, enumerate et reduce et une première fois en count et accumulate. Merci à tous!

Voici les résultats (à partir de mon lent netbook) en utilisant le logiciel, je suis en développement pour l'affichage:

┌───────────────────────────────────────────────────────┐
│                 Count and Sum Timing                  │
├──────────────────────────┬───────────┬────────────────┤
│          Method          │Time (usec)│Time (% of base)│
├──────────────────────────┼───────────┼────────────────┤
│count_and_sum (base)      │        7.2│            100%│
│Two sums                  │        7.5│            104%│
│deque enumerate accumulate│        7.3│            101%│
│max enumerate accumulate  │        7.3│            101%│
│reduce                    │        7.4│            103%│
│count sum                 │        7.3│            101%│
└──────────────────────────┴───────────┴────────────────┘

(Je n'ai pas le temps de la complexité et de plier les méthodes comme étant tout simplement trop obscure, mais merci quand même.)

Puisqu'il y a très peu de différence de timing entre toutes ces méthodes j'ai décidé d'utiliser l' count_and_sum de la fonction (explicite for boucle) comme étant le plus lisible, explicite et simple (Python Zen) et il arrive aussi d'être le plus rapide!

Je souhaite que je pourrais accepter l'une de ces étonnantes réponses correctes, mais ils sont tout aussi bon mais plus ou moins obscur, je suis juste en haut à droit de vote de tout le monde et accepter ma propre réponse comme correcte (count_and_sum de la fonction) puisque c'est ce que j'utilise.

Qu'était-ce à propos de "Il devrait y avoir un, et de préférence seulement une façon évidente de le faire."?

103voto

1_CR Points 11848

Utiliser des nombres complexes

 z = [1, 2, 4, 5, 6]
y = sum(x + 1j for x in z)
sum_z, count_z = y.real, int(y.imag)
print sum_z, count_z
18.0 5
 

49voto

DSM Points 71975

Je ne sais pas à propos de la vitesse, mais c'est assez joli:

 >>> from itertools import accumulate
>>> it = range(10)
>>> max(enumerate(accumulate(it), 1))
(10, 45)
 

29voto

M4rtini Points 5144

Adaptation de la réponse de DSM. utiliser deque(... maxlen=1) pour économiser la mémoire.

 import itertools 
from collections import deque 
deque(enumerate(itertools.accumulate(x), 1), maxlen=1)
 

code de synchronisation dans ipython:

 import itertools , random
from collections import deque 

def count_and_sum(iter):
     count = sum = 0
     for item in iter:
         count += 1
         sum += item
     return count, sum

X = [random.randint(0, 10) for _ in range(10**7)]
%timeit count_and_sum(X)
%timeit deque(enumerate(itertools.accumulate(X), 1), maxlen=1)
%timeit (max(enumerate(itertools.accumulate(X), 1)))
 

résultats: maintenant plus rapide que la méthode d'OP

 1 loops, best of 3: 1.08 s per loop
1 loops, best of 3: 659 ms per loop
1 loops, best of 3: 1.19 s per loop
 

23voto

senshin Points 4222

Voici quelques données de temps qui pourrait être d'intérêt:

import timeit

setup = '''
import random, functools, itertools, collections

x = [random.randint(0, 10) for _ in range(10**5)]

def count_and_sum(it):
    c, s = 0, 0
    for i in it:
        c += 1
        s += i
    return c, s

def two_pass(it):
    return sum(i for i in it), sum(True for i in it)

def functional(it):
    return functools.reduce(lambda pair, x: (pair[0]+1, pair[1]+x), it, [0, 0])

def accumulator(it):
    return max(enumerate(itertools.accumulate(it), 1))

def complex(it):
    cpx = sum(x + 1j for x in it)
    return cpx.real, int(cpx.imag)

def dequed(it):
    return collections.deque(enumerate(itertools.accumulate(it), 1), maxlen=1)

'''

number = 100
for stmt in ['count_and_sum(x)',
             'two_pass(x)',
             'functional(x)',
             'accumulator(x)',
             'complex(x)',
             'dequed(x)']:
    print('{:.4}'.format(timeit.timeit(stmt=stmt, setup=setup, number=number)))

Résultat:

3.404 # OP's one-pass method
3.833 # OP's two-pass method
8.405 # Timothy Shields's fold method
3.892 # DSM's accumulate-based method
4.946 # 1_CR's complex-number method
2.002 # M4rtini's deque-based modification of DSM's method

Compte tenu de ces résultats, je ne suis pas vraiment sûr de savoir comment les OP, c'est de voir une 100x ralentissement avec l'un passage de la méthode. Même si les données semblent radicalement différentes à partir d'une liste d'entiers aléatoires, qui ne doivent pas se produire.

Aussi, M4rtini la solution ressemble le gagnant clair.


Pour préciser, ces résultats sont en Disponible 3.2.3. Pour une comparaison à PyPy3, voir James_pic réponse, qui montre le peu de sérieux gains de compilation JIT pour certaines méthodes (également mentionné dans un commentaire par M4rtini.

20voto

James_pic Points 1684

Comme suite à l' senshin's réponse, il est intéressant de noter que les différences de performances sont en grande partie en raison de bizarreries dans Disponible de la mise en œuvre, qui font que certaines méthodes sont plus lents que d'autres (par exemple, for boucles sont relativement lent en Disponible). J'ai pensé qu'il serait intéressant d'essayer de le exactement le même test dans PyPy (à l'aide de PyPy3 2.1 beta), qui a des caractéristiques de performance différentes. Dans PyPy les résultats sont les suivants:

0.6227 # OP's one-pass method
0.8714 # OP's two-pass method
1.033 # Timothy Shields's fold method
6.354 # DSM's accumulate-based method
1.287 # 1_CR's complex-number method
3.857 # M4rtini's deque-based modification of DSM's method

Dans ce cas, l'OP est un passe-méthode est la plus rapide. Cela est logique, car c'est sans doute le plus simple (au moins à partir d'un compilateur de point de vue) et PyPy pouvez éliminer la plupart des frais généraux par l'in-lining appels de méthode, qui Disponible ne le peuvent pas.

Pour comparaison, Disponible 3.3.2 sur ma machine donne le texte suivant:

1.651 # OP's one-pass method
1.825 # OP's two-pass method
3.258 # Timothy Shields's fold method
1.684 # DSM's accumulate-based method
3.072 # 1_CR's complex-number method
1.191 # M4rtini's deque-based modification of DSM's method

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X