web-dev-qa-db-fra.com

python histogramme one-liner

Il existe de nombreuses façons d'écrire un programme Python qui calcule un histogramme.

Par histogramme, j'entends une fonction qui compte l'occurrence d'objets dans un iterable et renvoie les nombres 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

Existe-t-il des façons plus concises d'écrire cette fonction?

Si nous avions des compréhensions de dictionnaire en Python, nous pourrions écrire:

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

mais comme Python 2.6 n'en a pas, il faut écrire:

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

Bien que cette approche puisse être lisible, elle n'est pas efficace: L est parcouru plusieurs fois. De plus, cela ne fonctionnera pas pour les générateurs à vie unique; la fonction devrait fonctionner aussi bien pour les générateurs d'ité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!

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

Je termine avec:

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

(Dans Python 3, il faudrait écrire list(d.items()) au lieu de d.items(), mais c'est hypothétique, car il n'y a pas de reduce Là.)

S'il vous plaît, battez-moi avec une meilleure doublure, plus lisible! ;)

47
mykhal

Python 3.x a reduce, il suffit de faire un from functools import reduce. Il a également des "compréhensions dict", qui ont exactement la syntaxe de votre exemple.

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

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

Dans Python 2.6 ou antérieur, j'utiliserais personnellement un defaultdict et le ferais en 2 lignes:

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

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

76
Eli Courtwright

C'est un peu cheat d'importer des modules pour les oneliners, alors voici un oneliner qui est O(n) et qui fonctionne 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 __ les méthodes sont hacky, 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}

:)

7
John La Rooy
$d{$_} += 1 for split //, 'abracadabra';
6
perl
import pandas as pd

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

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

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

Celui qui fonctionne à 2,3 (légèrement plus court que celui de Timmerman, 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}
4
dgulino

J'avais besoin d'une implémentation d'histogramme pour travailler dans python 2.2 jusqu'à 2.7, et j'ai trouvé 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}

J'ai été inspiré par le post d'un défaut par Eli Courtwright. Ceux-ci ont été introduits dans python 2.5 ne peut donc pas être utilisé. Mais ils peuvent être émulés avec le dict.setdefault (clé, par défaut).

C'est essentiellement la même chose que gnibbler fait, mais j'ai dû l'écrire d'abord avant de pouvoir comprendre complètement sa fonction lambda.

1
Jens Timmerman

Votre one-liner utilisant reduce était presque correct, vous n'aviez besoin que de le modifier 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 les solutions sur place (ni en vitesse, ni en pythonicité), mais en échange, vous obtenez un extrait de code purement fonctionnel Nice. BTW, ce serait un peu plus joli si Python avait une méthode dict.merge().

1
tokland

Pendant un moment, tout ce qui utilisant itertools était par définition Pythonic. Pourtant, c'est un peu 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}

J'exécute actuellement Python 2.5.4.

1
PaulMcG