Question

Il y a plusieurs façons d'écrire un programme Python qui calcule un histogramme.

Par histogramme, je veux dire une fonction qui compte l'occurrence d'objets dans un iterable et sorties des comptes dans un dictionnaire. Par exemple:

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

Une façon d'écrire cette fonction est:

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

Y at-il des moyens plus concis de l'écriture de cette fonction?

Si nous avions compréhensions dictionnaire en Python, on pourrait écrire:

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

mais depuis Python 2.6 ne les a pas, il faut écrire:

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

Bien que cette approche peut être lisible, il est efficace: L est entré par-plusieurs fois. De plus, cela ne fonctionnera pas pour les générateurs unique de la vie; la fonction doit également bien fonctionner pour itérateur générateurs tels que:

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

Nous pourrions essayer d'utiliser la fonction reduce (R.I.P.):

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

Oops, cela ne fonctionne pas: le nom de clé est 'x', pas x. : (

J'ai fini avec:

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

(En Python 3, nous devrions écrire list(d.items()) au lieu de d.items(), mais il est hypothethical, puisqu'il n'y a pas reduce là.)

S'il vous plaît me battre avec une meilleure, plus facile à lire en une ligne! ;)

Était-ce utile?

La solution

Python 3.x n'ont reduce, il vous suffit de faire un from functools import reduce. Il a également « dict compréhensions », qui ont exactement la syntaxe dans votre exemple.

Python 2.7 et 3.x ont également contre classe qui fait exactement ce que vous voulez:

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

En Python 2.6 ou plus tôt, je voudrais personnellement utiliser un defaultdict et de le faire en 2 lignes:

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

C'est propre, efficace, Pythonic et beaucoup plus facile pour la plupart des gens à comprendre que tout ce qui concerne reduce.

Autres conseils

Il est un peu cheaty aux modules d'importation pour Citations, alors voici une oneliner qui est O (n) et de travaux au moins aussi loin que 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}

Et si vous pensez que les méthodes de __ sont aki, vous pouvez toujours le faire

>>> 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()

Pour Python 2.7, vous pouvez utiliser cette petite compréhension de la liste:

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

Celui qui fonctionne en arrière à 2,3 (légèrement plus courte que Timmerman de, je pense plus lisible):

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}

Pendant un certain temps, tout en utilisant itertools était par définition Pythonic. Pourtant, cela est un peu sur le côté opaque:

>>> 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}

Je suis actuellement en cours d'exécution Python 2.5.4.

Votre utilisation reduce un liner était presque correct, vous ne fallait ruser un peu:

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

Bien sûr, cela ne battra pas des solutions en place (ni la vitesse, ni dans pythonicity), mais en échange vous avez vous-même une belle purement fonctionnelle extrait. BTW, ce serait un peu plus joli si Python avait une méthode dict.merge().

Je besoin d'une mise en œuvre de l'histogramme en Python 2.2 à 2.7, et est venu avec ceci:

>>> 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}

Je suis inspiré par le poste d'Eli Courtwright d'un defaultdict. Ceux-ci ont été introduites en Python 2.5 ne peut donc pas être utilisés. Mais ils peuvent être émulés avec le dict.setdefault (clé, valeur par défaut).

Ceci est fondamentalement la même chose gnibbler fait, mais je devais écrire ce premier avant que je puisse comprendre complètement sa fonction lambda.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top