Pregunta

Hay muchas formas de escribir un programa Python que calcula un histograma.

Por histograma, me refiero a una función que cuenta la aparición de los objetos de una iterable y salidas de los recuentos en un diccionario. Por ejemplo:

>>> L = 'abracadabra'
>>> histogram(L)
{'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2}

Una forma de escribir esta función es:

def histogram(L):
    d = {}
    for x in L:
        if x in d:
            d[x] += 1
        else:
            d[x] = 1
    return d

¿Hay formas más concisas de escribir esta función?

Si tuviéramos comprensiones del diccionario en Python, podríamos escribir:

>>> { x: L.count(x) for x in set(L) }

pero desde Python 2.6 no los tiene, tenemos que escribir:

>>> dict([(x, L.count(x)) for x in set(L)])

A pesar de que este enfoque puede ser legible, no es eficiente: L es caminaba a través de varias veces. Por otra parte, esto no funcionará para los generadores de una sola vida; la función debería funcionar igual de bien para iterador generadores tales como:

def gen(L):
    for x in L:
        yield x

podría tratar de utilizar la función reduce (R.I.P.):

>>> reduce(lambda d,x: dict(d, x=d.get(x,0)+1), L, {}) # wrong!

Vaya, esto no funciona: el nombre clave es 'x', no x. : (

terminé con:

>>> reduce(lambda d,x: dict(d.items() + [(x, d.get(x, 0)+1)]), L, {})

(En Python 3, tendríamos que list(d.items()) escritura en lugar de d.items(), pero es hypothethical, ya que no hay reduce allí.)

Por favor, me golpeó con un mejor, más fácil de leer de una sola línea! ;)

¿Fue útil?

Solución

Python 3.x tiene reduce, sólo hay que hacer un from functools import reduce. También tiene por comprensión "dict", que tienen exactamente la sintaxis en su ejemplo.

Python 2.7 y 3.x también tienen un Contador clase que hace exactamente lo que quiere:

from collections import Counter
cnt = Counter("abracadabra")

En Python 2.6 o anterior, usaría personalmente a un defaultdict y hacerlo en 2 líneas:

d = defaultdict(int)
for x in xs: d[x] += 1

Eso es limpio, eficiente, Pythonic, y mucho más fácil para la mayoría de la gente a entender que cualquier cosa que implica reduce.

Otros consejos

Es un poco cheaty a los módulos de importación para oneliners, así que aquí tiene un oneliner que es O (n) y las obras al menos tan atrás como python2.4

>>> f=lambda s,d={}:([d.__setitem__(i,d.get(i,0)+1) for i in s],d)[-1]
>>> f("ABRACADABRA")
{'A': 5, 'R': 2, 'B': 2, 'C': 1, 'D': 1}

Y si usted piensa métodos __ son hacky, siempre se puede hacer esto

>>> f=lambda s,d=lambda:0:vars(([setattr(d,i,getattr(d,i,0)+1) for i in s],d)[-1])
>>> f("ABRACADABRA")
{'A': 5, 'R': 2, 'B': 2, 'C': 1, 'D': 1}

:)

$d{$_} += 1 for split //, 'abracadabra';
import pandas as pd

pd.Series(list(L)).value_counts()

En Python 2.7, se puede utilizar esta lista pequeña de comprensión:

v = list('abracadabra')
print {x: v.count(x) for x in set(v)}

Uno que funciona de nuevo a 2.3 (ligeramente más corto que Timmerman de, creo que más legible):

L = 'abracadabra'
hist = {}
for x in L: hist[x] = hist.pop(x,0) + 1
print hist
{'a': 5, 'r': 2, 'b': 2, 'c': 1, 'd': 1}

Durante un tiempo, cualquier cosa usando itertools era por definición Pythonic. Sin embargo, esto es un poco en el lado opaco:

>>> from itertools import groupby
>>> grouplen = lambda grp : sum(1 for i in grp)
>>> hist = dict((a[0], grouplen(a[1])) for a in groupby(sorted("ABRACADABRA")))
>>> print hist
{'A': 5, 'R': 2, 'C': 1, 'B': 2, 'D': 1}

Estoy actualmente en ejecución Python 2.5.4.

Su sola línea usando reduce estaba casi bien, sólo tenía que ajustar un poco:

>>> reduce(lambda d, x: dict(d, **{x: d.get(x, 0) + 1}), L, {})
{'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2}

Por supuesto, esto no va a superar soluciones in situ (ni en la velocidad, ni en pythonicity), pero a cambio usted se tiene un buen fragmento puramente funcional. Por cierto, esto sería un poco más bonito si tenía un pitón dict.merge() método.

Yo necesitaba una aplicación histograma para el trabajo en Python 2.2 hasta 2.7, y se acercó con esto:

>>> L = 'abracadabra'
>>> hist = {}
>>> for x in L: hist[x] = hist.setdefault(x,0)+1
>>> print hist
{'a': 5, 'r': 2, 'b': 2, 'c': 1, 'd': 1}

Me inspiré en un puesto de defaultdict de Eli Courtwright. Estos se introdujeron en pitón 2,5 por lo que no pueden ser utilizados. Sin embargo, pueden ser emulados con el dict.setdefault (clave, valor predeterminado).

Esto es básicamente lo mismo gnibbler está haciendo, pero tenía que escribir esta primera antes de que pudiera comprender por completo su función lambda.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top