La forma más python de contar elementos coincidentes en algo iterable.

Tengo un número de entradas en las que me gustaría recostackr algunas estadísticas simples, por ejemplo, el recuento de todos los números divisibles por dos y el de todos los números divisibles por tres.

Mi primera alternativa, aunque solo recorre la lista una vez y evite la expansión de la lista (y teniendo en cuenta la refactorización de bucle dividido ), parece bastante abultado:

(alt 1)

r = xrange(1, 10) twos = 0 threes = 0 for v in r: if v % 2 == 0: twos+=1 if v % 3 == 0: threes+=1 print twos print threes 

Esto se ve bastante bien, pero tiene el inconveniente de expandir la expresión a una lista:

(alt 2)

 r = xrange(1, 10) print len([1 for v in r if v % 2 == 0]) print len([1 for v in r if v % 3 == 0]) 

Lo que realmente me gustaría es algo como una función como esta:

(alt 3)

 def count(iterable): n = 0 for i in iterable: n += 1 return n r = xrange(1, 10) print count(1 for v in r if v % 2 == 0) print count(1 for v in r if v % 3 == 0) 

Pero esto se parece mucho a algo que se podría hacer sin una función. La variante final es esta:

(alt 4)

 r = xrange(1, 10) print sum(1 for v in r if v % 2 == 0) print sum(1 for v in r if v % 3 == 0) 

y mientras que el más pequeño (y en mi libro probablemente el más elegante) no parece que exprese la intención muy bien.

Por lo tanto, mi pregunta para usted es:

¿Qué alternativa te gusta más para recostackr este tipo de estadísticas? Siéntase libre de proporcionar su propia alternativa si tiene algo mejor.

Para aclarar alguna confusión a continuación:

  • En realidad, mis predicados de filtro son más complejos que solo esta simple prueba.
  • Los objetos sobre los que hago iteraciones son más grandes y complejos que solo números
  • Mis funciones de filtro son más diferentes y difíciles de parametrizar en un predicado

Tener que recorrer la lista varias veces no es elegante en mi humilde opinión.

Probablemente crearía una función que permita hacer:

 twos, threes = countmatching(xrange(1,10), lambda a: a % 2 == 0, lambda a: a % 3 == 0) 

Un punto de partida sería algo como esto:

 def countmatching(iterable, *predicates): v = [0] * len(predicates) for e in iterable: for i,p in enumerate(predicates): if p(e): v[i] += 1 return tuple(v) 

Por cierto, “recetas de itertools” tiene una receta para hacer como su alt4.

 def quantify(seq, pred=None): "Count how many times the predicate is true in the sequence" return sum(imap(pred, seq)) 

Alt 4! Pero tal vez debería refactorizar el código a una función que tome un argumento que debería contener el número divisible (dos y tres). Y entonces podrías tener un mejor nombre de función.

 def methodName(divNumber, r): return sum(1 for v in r if v % divNumber == 0) print methodName(2, xrange(1, 10)) print methodName(3, xrange(1, 10)) 

Podrías usar la función de filter .

Filtra una lista (o estrictamente un iterable) produciendo una nueva lista que contiene solo los elementos para los cuales la función especificada se evalúa como verdadera.

 r = xrange(1, 10) def is_div_two(n): return n % 2 == 0 def is_div_three(n): return n % 3 == 0 print len(filter(is_div_two,r)) print len(filter(is_div_three,r)) 

Esto es bueno, ya que le permite mantener la lógica de las estadísticas contenida en una función y la intención del filter debe ser bastante clara.

Elegiría una pequeña variante de tu (alt 4):

 def count(predicate, list): print sum(1 for x in list if predicate(x)) r = xrange(1, 10) count(lambda x: x % 2 == 0, r) count(lambda x: x % 3 == 0, r) # ... 

Si desea cambiar lo que hace el conteo, cambie su implementación en un solo lugar.

Nota: ya que sus predicados son complejos, probablemente querrá definirlos en funciones en lugar de lambdas. Y, entonces, probablemente querrá poner todo esto en una clase en lugar del espacio de nombres global.

Bueno, podrías hacer una lista de comprensión / expresión para obtener un conjunto de tuplas con esa prueba estadística en ellas y luego reducirla para obtener las sums.

 r=xrange(10) s=( (v % 2 == 0, v % 3 == 0) for v in r ) def add_tuples(t1,t2): return tuple(x+y for x,y in zip(t1, t2)) sums=reduce(add_tuples, s, (0,0)) # (0,0) is starting amount print sums[0] # sum of numbers divisible by 2 print sums[1] # sum of numbers divisible by 3
r=xrange(10) s=( (v % 2 == 0, v % 3 == 0) for v in r ) def add_tuples(t1,t2): return tuple(x+y for x,y in zip(t1, t2)) sums=reduce(add_tuples, s, (0,0)) # (0,0) is starting amount print sums[0] # sum of numbers divisible by 2 print sums[1] # sum of numbers divisible by 3 

El uso de la expresión del generador, etc., debe significar que solo se ejecutará a través del iterador una vez (a menos que reducir haga algo extraño). Básicamente estarías haciendo mapa / reducir …

Los verdaderos booleanos están obligados a enteros unitarios, y los falsos booleanos a cero enteros. Por lo tanto, si está contento de usar scipy o numpy, haga una matriz de enteros para cada elemento de su secuencia, cada matriz que contenga un elemento para cada una de sus pruebas, y sume las matrices. P.ej

 >>> sum(scipy.array([c % 2 == 0, c % 3 == 0]) for c in xrange(10)) array([5, 4]) 

Definitivamente, me gustaría ver una matriz numpy en lugar de una lista iterable si solo tienes números. Es casi seguro que podrá hacer lo que quiera con un poco de aritmética en la matriz.

No es tan conciso como lo está buscando, pero más eficiente, en realidad funciona con cualquier iterable, no solo con iterables que puede repasar varias veces, y puede expandir las cosas para verificar sin complicar aún más:

 r = xrange(1, 10) counts = { 2: 0, 3: 0, } for v in r: for q in counts: if not v % q: counts[q] += 1 # Or, more obscure: #counts[q] += not v % q for q in counts: print "%s's: %s" % (q, counts[q]) 
 from itertools import groupby from collections import defaultdict def multiples(v): return 2 if v%2==0 else 3 if v%3==0 else None d = defaultdict(list) for k, values in groupby(range(10), multiples): if k is not None: d[k].extend(values) 

Inspirado por el OO-stab anterior, tuve que probar mis manos en uno también (aunque esto es demasiado para el problema que estoy tratando de resolver 🙂

 class Stat(object): def update(self, n): raise NotImplementedError def get(self): raise NotImplementedError class TwoStat(Stat): def __init__(self): self._twos = 0 def update(self, n): if n % 2 == 0: self._twos += 1 def get(self): return self._twos class ThreeStat(Stat): def __init__(self): self._threes = 0 def update(self, n): if n % 3 == 0: self._threes += 1 def get(self): return self._threes class StatCalculator(object): def __init__(self, stats): self._stats = stats def calculate(self, r): for v in r: for stat in self._stats: stat.update(v) return tuple(stat.get() for stat in self._stats) s = StatCalculator([TwoStat(), ThreeStat()]) r = xrange(1, 10) print s.calculate(r) 

Alt 3, por la razón de que no utiliza la memoria proporcional al número de “hits”. Dado un caso patológico como xrange (un billón), muchas de las otras soluciones ofrecidas fallarían gravemente.

La idea aquí es usar la reducción para evitar repeticiones repetidas. Además, esto no crea ninguna estructura de datos adicional, si la memoria es un problema para usted. Comienzas con un diccionario con tus contadores ( {'div2': 0, 'div3': 0} ) y los incrementas a lo largo de la iteración.

 def increment_stats(stats, n): if n % 2 == 0: stats['div2'] += 1 if n % 3 == 0: stats['div3'] += 1 return stats r = xrange(1, 10) stats = reduce(increment_stats, r, {'div2': 0, 'div3': 0}) print stats 

Si desea contar algo más complicado que los divisores, sería apropiado utilizar un enfoque más orientado a objetos (con las mismas ventajas), encapsulando la lógica para la extracción de estadísticas.

 class Stats: def __init__(self, div2=0, div3=0): self.div2 = div2 self.div3 = div3 def increment(self, n): if n % 2 == 0: self.div2 += 1 if n % 3 == 0: self.div3 += 1 return self def __repr__(self): return 'Stats(%d, %d)' % (self.div2, self.div3) r = xrange(1, 10) stats = reduce(lambda stats, n: stats.increment(n), r, Stats()) print stats 

Por favor, señale cualquier error.

@Henrik: Creo que el primer enfoque es menos mantenible, ya que tiene que controlar la inicialización del diccionario en un lugar y actualizar en otro, así como tener que usar cadenas para referirse a cada estadística (en lugar de tener atributos). Y no creo que OO sea una exageración en este caso, ya que dijo que los predicados y los objetos serán complejos en su aplicación. De hecho, si los predicados fueran realmente simples, ni siquiera me molestaría en usar un diccionario, una sola lista de tamaño fijo estaría bien. Saludos 🙂