гистограмма питона, одна строка - PullRequest
47 голосов
/ 20 мая 2010

Существует множество способов написания программы на Python, которая вычисляет гистограмму.

Под гистограммой я подразумеваю функцию, которая считает количество объектов в iterable и выводит значения в словаре. Например:

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

Один из способов написать эту функцию:

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

Существуют ли более краткие способы написания этой функции?

Если бы у нас были словарные выражения в Python, мы могли бы написать:

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

но поскольку в Python 2.6 их нет, мы должны написать:

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

Хотя этот подход может быть читабельным, он неэффективен: L обходится несколько раз. Кроме того, это не будет работать для генераторов с одиночной жизнью; функция должна одинаково хорошо работать для генераторов итераторов, таких как:

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

Мы можем попытаться использовать функцию reduce (R.I.P.):

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

Упс, это не работает: имя ключа 'x', а не x. (

Я закончил с:

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

(В Python 3 нам нужно было бы написать list(d.items()) вместо d.items(), но это гипотетически, поскольку там нет reduce.)

Пожалуйста, побейте меня лучшей, более читаемой строчкой! ;)

Ответы [ 9 ]

76 голосов
/ 20 мая 2010

Python 3.x имеет reduce, вам просто нужно сделать from functools import reduce. У него также есть «вмятины», которые в вашем примере имеют именно такой синтаксис.

Python 2.7 и 3.x также имеют класс Counter , который делает именно то, что вы хотите:

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

В Python 2.6 или более ранней версии я лично использовал бы defaultdict и делал бы это в 2 строки:

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

Это чисто, эффективно, Pythonic, и для большинства людей гораздо проще понять, чем что-либо, связанное с reduce.

7 голосов
/ 18 августа 2010

импортировать модули для oneliners довольно обманчиво, так что вот oneliner, который является O (n) и работает по крайней мере еще с 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}

И если вы думаете, __ методы взломаны, вы всегда можете сделать это

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

:)

6 голосов
/ 20 февраля 2015
import pandas as pd

pd.Series(list(L)).value_counts()
6 голосов
/ 18 ноября 2010
$d{$_} += 1 for split //, 'abracadabra';
5 голосов
/ 16 августа 2013

Для Python 2.7 вы можете использовать этот небольшой список:

v = list('abracadabra')
print {x: v.count(x) for x in set(v)}
4 голосов
/ 13 декабря 2012

Тот, который работает до 2,3 (немного короче, чем Тиммерман, я думаю, более читабельным):

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}
1 голос
/ 21 февраля 2012

Мне нужна была реализация гистограммы для работы с Python 2.2 до 2.7, и я придумал это:

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

Я был вдохновлен сообщением Эли Кортрайта о дефолте. Они были введены в Python 2.5, поэтому не могут быть использованы. Но их можно эмулировать с помощью dict.setdefault (ключ, по умолчанию).

Это в основном то же самое, что делает gnibbler, но я должен был написать это прежде, чем смог полностью понять его лямбда-функцию.

1 голос
/ 06 сентября 2010

Ваш однострочник, использующий reduce, был почти нормальным, вам нужно было только немного его настроить:

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

Конечно, это не превзойдет решения на месте (ни по скорости, ни по питоничности), но взамен вы получите хороший чисто функциональный фрагмент. Кстати, это было бы несколько лучше, если бы у Python был метод dict.merge().

1 голос
/ 20 мая 2010

В течение некоторого времени все, что использовало itertools, было по определению Pythonic. Тем не менее, это немного на непрозрачной стороне:

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

Я сейчас использую Python 2.5.4.

...