Гистограмма python однострочный

Существует много способов написать программу 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.)

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

Ответ 1

У 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 и делал это в двух строках:

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

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

Ответ 2

Это нечестно импортировать модули для 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}

:)

Ответ 3

$d{$_} += 1 for split //, 'abracadabra';

Ответ 4

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

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

Ответ 5

import pandas as pd

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

Ответ 6

Тот, который работает до 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}

Ответ 7

Некоторое время все, используя 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.

Ответ 8

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

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

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

Ответ 9

Мне нужна была реализация гистограммы для работы в 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, но мне пришлось написать это сначала, прежде чем я смог полностью понять его лямбда-функцию.