Domanda

Ci sono molti modi per scrivere un programma Python che calcola l'istogramma.

Per istogramma, intendo una funzione che conta il verificarsi di oggetti in un iterable e uscite i conteggi in un dizionario. Ad esempio:

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

Un modo di scrivere questa funzione è:

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

Ci sono modi più concisi di scrivere questa funzione?

Se avessimo comprensioni del dizionario in Python, potremmo scrivere:

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

ma dal momento che Python 2.6 non ha loro, dobbiamo scrivere:

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

Anche se questo approccio può essere leggibile, non è efficiente: L è camminato-through più volte. Inoltre, questo non funzionerà per generatori singola vita; la funzione dovrebbe funzionare ugualmente bene per iterator_traits generatori quali:

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

potrebbe tentare di utilizzare la funzione reduce (R.I.P.):

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

Spiacenti, questo non funziona: il nome della chiave è 'x', non x. : (

Ho finito con:

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

(In Python 3, avremmo a list(d.items()) scrittura invece di d.items(), ma è hypothethical, dal momento che non v'è alcuna reduce lì.)

Per favore mi ha battuto con una migliore, più leggibile one-liner! ;)

È stato utile?

Soluzione

Python 3.x ha reduce, devi solo fare un from functools import reduce. Ha anche "comprensioni dict", che hanno esattamente la sintassi nel tuo esempio.

Python 2.7 e 3.x hanno anche un contatore classe che fa esattamente ciò che si desidera:

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

In Python 2.6 o versioni precedenti, userei personalmente un defaultdict e farlo in 2 linee:

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

Questo è pulito, efficiente, Pythonic, e molto più facile per la maggior parte le persone a capire che tutto ciò che coinvolge reduce.

Altri suggerimenti

E 'un po cheaty ai moduli di importazione per oneliners, quindi ecco un'oneliner che è O (n) e le opere, almeno fin da 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}

E se si pensa metodi __ sono hacky, si può sempre fare questo

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

per Python 2.7, è possibile utilizzare questo elenco piccola di comprensione:

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

Uno che funziona di nuovo a 2,3 (leggermente più corta Timmerman di, credo più leggibile):

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}

Per un po ', tutto ciò utilizzando itertools era per definizione Pythonic. Ancora, questo è un po 'sul lato 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}

Sono attualmente in esecuzione Python 2.5.4.

L'one-liner utilizzando reduce era quasi ok, è solo bisogno di modificarlo un po ':

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

Naturalmente, questo non battere le soluzioni in-place (né in termini di velocità, né pythonicity), ma in cambio ti sei preso una bella puramente frammento funzionale. BTW, questo sarebbe un po 'più bello se Python ha avuto un metodo dict.merge().

Ho bisogno di un'implementazione istogramma per il lavoro in Python 2.2 fino a 2.7, e si avvicinò con questo:

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

Sono stato ispirato dal post di Eli Courtwright di un defaultdict. Queste sono state introdotte in Python 2.5 in modo da non possono essere utilizzati. Ma possono essere emulate con la dict.setdefault (chiave, impostazione predefinita).

Questa è fondamentalmente la stessa cosa gnibbler sta facendo, ma ho dovuto scrivere questo primo prima di riuscire a comprendere appieno la sua funzione lambda.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top