tutorial for español else compressed comprehension python list-comprehension

python - for - La manera más pítica de contar elementos coincidentes en algo iterable



python list comprehension tutorial (12)

No es tan escueto como lo que está buscando, pero es más eficiente, en realidad funciona con iterables, no solo iterables sobre los que puede repetir repetidas veces, y puede expandir las cosas para verificar sin complicarlo 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])

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

Mi primera alternativa, aunque solo iterar por la lista una vez y evitar la expansión de la lista (y tener en cuenta la refactorización de bucle dividido ), parece bastante abotagada:

(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 así 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 podría hacerse 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 si bien es el más pequeño (y en mi libro probablemente el más elegante), no parece expresarse muy bien.

Entonces, mi pregunta para ti es:

¿Qué alternativa te gusta más para reunir este tipo de estadísticas? Siéntase libre de suministrar 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 iterar son más grandes y más complejos que solo los números
  • Mis funciones de filtro son más diferentes y difíciles de parametrizar en un predicado

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


Alt 4! Pero tal vez deberías refactorizar el código a una función que toma un argumento que debe contener el número divisible (dos y tres). Y luego 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))


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 sumas.

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. debería 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 map / reduce ...


Definitivamente estaría buscando 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.


Inspirado por el OO-stab anterior, tuve que probar mis manos en uno también (aunque esto es demasiado exagerado 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)


La idea aquí es usar reducción para evitar iteraciones repetidas. Además, esto no crea ninguna estructura de datos adicional, si la memoria es un problema para usted. Comienza con un diccionario con sus contadores ( {''div2'': 0, ''div3'': 0} ) y los incrementa 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 usar 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 sostenible ya que tienes que controlar la inicialización del diccionario en un lugar y actualizarlo en otro, así como tener que usar cadenas para hacer referencia a cada estadística (en lugar de tener atributos). Y no creo que OO sea excesivo 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 :)


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

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


Podría usar la función de filter .

Filtra una lista (o estrictamente iterable) produciendo una nueva lista que contiene solo los elementos para los que 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 su lógica de estadísticas contenida en una función y la intención del filter debe ser bastante clara.


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

Probablemente crearía una función que permite 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, "itertools recetas" tiene una receta para hacer mucho como su alt4.

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


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)


Yo 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 cuenta, cambie su implementación en un solo lugar.

Nota: dado que sus predicados son complejos, probablemente quiera definirlos en funciones en lugar de lambdas. Por lo tanto, probablemente querrá poner todo esto en una clase en lugar del espacio de nombres global.