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