Python. Медленно ли словарь находит частоту каждого символа?

Я пытаюсь найти частоту каждого символа в любом заданном тексте, используя алгоритм сложности O (n). Мой алгоритм выглядит так:

s = len(text) 
P = 1.0/s 
freqs = {} 
for char in text: 
    try: 
       freqs[char]+=P 
    except: 
       freqs[char]=P 

но я сомневаюсь, что этот метод словаря достаточно быстр, потому что он зависит от базовой реализации методов словаря. Это самый быстрый способ?

ОБНОВЛЕНИЕ: нет увеличения скорости, если используются коллекции и целые числа. Это связано с тем, что алгоритм уже имеет сложность O (n), поэтому существенное ускорение невозможно.

Например, результаты для текста размером 1 МБ:

without collections:
real    0m0.695s

with collections:
real    0m0.625s

person psihodelia    schedule 26.03.2010    source источник
comment
Словарные операции используют хэши и выполняются за O(1). Как это может быть недостаточно быстро? Что вы имеете в виду под достаточно быстро? Что вы измерили? Какова твоя цель?   -  person S.Lott    schedule 26.03.2010
comment
С какой стати вы используете для этого плавающую точку?   -  person S.Lott    schedule 26.03.2010
comment
@psihodelia Philosoraptor однажды сказал: используйте целое число для целочисленной математики.   -  person orokusaki    schedule 26.03.2010


Ответы (12)


Сравнение производительности

Примечание: время в таблице не включает время, необходимое для загрузки файлов.

| approach       | american-english, |      big.txt, | time w.r.t. defaultdict |
|                |     time, seconds | time, seconds |                         |
|----------------+-------------------+---------------+-------------------------|
| Counter        |             0.451 |         3.367 |                     3.6 |
| setdefault     |             0.348 |         2.320 |                     2.5 |
| list           |             0.277 |         1.822 |                       2 |
| try/except     |             0.158 |         1.068 |                     1.2 |
| defaultdict    |             0.141 |         0.925 |                       1 |
| numpy          |             0.012 |         0.076 |                   0.082 |
| S.Mark's ext.  |             0.003 |         0.019 |                   0.021 |
| ext. in Cython |             0.001 |         0.008 |                  0.0086 |
#+TBLFM: $4=$3/@7$3;%.2g

Используемые файлы: '/usr/share/dict/american-english' и 'big.txt'.

Сценарий, который сравнивает решения «Counter», «setdefault», «list», «try/except», «defaultdict», «numpy», «cython» и решения @S.Mark, находится по адресу http://gist.github.com/347000

Самое быстрое решение — это расширение Python, написанное на Cython:

import cython

@cython.locals(
    chars=unicode,
    i=cython.Py_ssize_t,
    L=cython.Py_ssize_t[0x10000])
def countchars_cython(chars):
    for i in range(0x10000): # unicode code points > 0xffff are not supported
        L[i] = 0

    for c in chars:
        L[c] += 1

    return {unichr(i): L[i] for i in range(0x10000) if L[i]}

Предыдущее сравнение:

* python (dict) : 0.5  seconds
* python (list) : 0.5  (ascii) (0.2 if read whole file in memory)
* perl          : 0.5
* python (numpy): 0.07 
* c++           : 0.05
* c             : 0.008 (ascii)

Входные данные:

$ tail /usr/share/dict/american-english
éclat's
élan
élan's
émigré
émigrés
épée
épées
étude
étude's
études

$ du -h /usr/share/dict/american-english
912K    /usr/share/dict/american-english

питон (счетчик): 0,5 секунды

#!/usr/bin/env python3.1
import collections, fileinput, textwrap

chars = (ch for word in fileinput.input() for ch in word.rstrip())
# faster (0.4s) but less flexible: chars = open(filename).read()
print(textwrap.fill(str(collections.Counter(chars)), width=79))

Запустить его:

$ time -p python3.1 count_char.py /usr/share/dict/american-english
Counter({'e': 87823, 's': 86620, 'i': 66548, 'a': 62778, 'n': 56696, 'r':
56286, 't': 51588, 'o': 48425, 'l': 39914, 'c': 30020, 'd': 28068, 'u': 25810,
"'": 24511, 'g': 22262, 'p': 20917, 'm': 20747, 'h': 18453, 'b': 14137, 'y':
12367, 'f': 10049, 'k': 7800, 'v': 7573, 'w': 6924, 'z': 3088, 'x': 2082, 'M':
1686, 'C': 1549, 'S': 1515, 'q': 1447, 'B': 1387, 'j': 1376, 'A': 1345, 'P':
974, 'L': 912, 'H': 860, 'T': 858, 'G': 811, 'D': 809, 'R': 749, 'K': 656, 'E':
618, 'J': 539, 'N': 531, 'W': 507, 'F': 502, 'O': 354, 'I': 344, 'V': 330, 'Z':
150, 'Y': 140, 'é': 128, 'U': 117, 'Q': 63, 'X': 42, 'è': 29, 'ö': 12, 'ü': 12,
'ó': 10, 'á': 10, 'ä': 7, 'ê': 6, 'â': 6, 'ñ': 6, 'ç': 4, 'å': 3, 'û': 3, 'í':
2, 'ô': 2, 'Å': 1})
real 0.44
user 0.43
sys 0.01

перл: 0,5 секунды

time -p perl -MData::Dumper -F'' -lanwe'$c{$_}++ for (@F);
END{ $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; print Dumper(\%c) }
' /usr/share/dict/american-english

Вывод:

{'S' => 1515,'K' => 656,'' => 29,'d' => 28068,'Y' => 140,'E' => 618,'y' => 12367,'g' => 22262,'e' => 87823,'' => 2,'J' => 539,'' => 241,'' => 3,'' => 6,'' => 4,'' => 128,'D' => 809,'q' => 1447,'b' => 14137,'z' => 3088,'w' => 6924,'Q' => 63,'' => 10,'M' => 1686,'C' => 1549,'' => 10,'L' => 912,'X' => 42,'P' => 974,'' => 12,'\'' => 24511,'' => 6,'a' => 62778,'T' => 858,'N' => 531,'j' => 1376,'Z' => 150,'u' => 25810,'k' => 7800,'t' => 51588,'' => 6,'W' => 507,'v' => 7573,'s' => 86620,'B' => 1387,'H' => 860,'c' => 30020,'' => 12,'I' => 344,'' => 3,'G' => 811,'U' => 117,'F' => 502,'' => 2,'r' => 56286,'x' => 2082,'V' => 330,'h' => 18453,'f' => 10049,'' => 1,'i' => 66548,'A' => 1345,'O' => 354,'n' => 56696,'m' => 20747,'l' => 39914,'' => 7,'p' => 20917,'R' => 749,'o' => 48425}
real 0.51
user 0.49
sys 0.02

питон (numpy): 0,07 секунды

На основе Ants Aasma answer (модифицировано для поддержки юникода):

#!/usr/bin/env python
import codecs, itertools, operator, sys
import numpy

filename = sys.argv[1] if len(sys.argv)>1 else '/usr/share/dict/american-english'

# ucs2 or ucs4 python?
dtype = {2: numpy.uint16, 4: numpy.uint32}[len(buffer(u"u"))]

# count ordinals
text = codecs.open(filename, encoding='utf-8').read()
a = numpy.frombuffer(text, dtype=dtype)
counts = numpy.bincount(a)

# pretty print
counts = [(unichr(i), v) for i, v in enumerate(counts) if v]
counts.sort(key=operator.itemgetter(1))
print ' '.join('("%s" %d)' % c for c in counts  if c[0] not in ' \t\n')

Вывод:

("Å" 1) ("í" 2) ("ô" 2) ("å" 3) ("û" 3) ("ç" 4) ("â" 6) ("ê" 6) ("ñ" 6) ("ä" 7) ("á" 10) ("ó" 10) ("ö" 12) ("ü" 12) ("è" 29) ("X" 42) ("Q" 63) ("U" 117) ("é" 128) ("Y" 140) ("Z" 150) ("V" 330) ("I" 344) ("O" 354) ("F" 502) ("W" 507) ("N" 531) ("J" 539) ("E" 618) ("K" 656) ("R" 749) ("D" 809) ("G" 811) ("T" 858) ("H" 860) ("L" 912) ("P" 974) ("A" 1345) ("j" 1376) ("B" 1387) ("q" 1447) ("S" 1515) ("C" 1549) ("M" 1686) ("x" 2082) ("z" 3088) ("w" 6924) ("v" 7573) ("k" 7800) ("f" 10049) ("y" 12367) ("b" 14137) ("h" 18453) ("m" 20747) ("p" 20917) ("g" 22262) ("'" 24511) ("u" 25810) ("d" 28068) ("c" 30020) ("l" 39914) ("o" 48425) ("t" 51588) ("r" 56286) ("n" 56696) ("a" 62778) ("i" 66548) ("s" 86620) ("e" 87823)
real 0.07
user 0.06
sys 0.01

С++: 0,05 секунды

// $ g++ *.cc -lboost_program_options 
// $ ./a.out /usr/share/dict/american-english    
#include <iostream>
#include <fstream>
#include <cstdlib> // exit

#include <boost/program_options/detail/utf8_codecvt_facet.hpp>
#include <boost/tr1/unordered_map.hpp>
#include <boost/foreach.hpp>

int main(int argc, char* argv[]) {
  using namespace std;

  // open input file
  if (argc != 2) {
    cerr << "Usage: " << argv[0] << " <filename>\n";
    exit(2);
  }
  wifstream f(argv[argc-1]); 

  // assume the file has utf-8 encoding
  locale utf8_locale(locale(""), 
      new boost::program_options::detail::utf8_codecvt_facet);
  f.imbue(utf8_locale); 

  // count characters frequencies
  typedef std::tr1::unordered_map<wchar_t, size_t> hashtable_t;  
  hashtable_t counts;
  for (wchar_t ch; f >> ch; )
    counts[ch]++;
  
  // print result
  wofstream of("output.utf8");
  of.imbue(utf8_locale);
  BOOST_FOREACH(hashtable_t::value_type i, counts) 
    of << "(" << i.first << " " << i.second << ") ";
  of << endl;
}

Результат:

$ cat output.utf8 
(í 2) (O 354) (P 974) (Q 63) (R 749) (S 1,515) (ñ 6) (T 858) (U 117) (ó 10) (ô 2) (V 330) (W 507) (X 42) (ö 12) (Y 140) (Z 150) (û 3) (ü 12) (a 62,778) (b 14,137) (c 30,020) (d 28,068) (e 87,823) (f 10,049) (g 22,262) (h 18,453) (i 66,548) (j 1,376) (k 7,800) (l 39,914) (m 20,747) (n 56,696) (o 48,425) (p 20,917) (q 1,447) (r 56,286) (s 86,620) (t 51,588) (u 25,810) (Å 1) (' 24,511) (v 7,573) (w 6,924) (x 2,082) (y 12,367) (z 3,088) (A 1,345) (B 1,387) (C 1,549) (á 10) (â 6) (D 809) (E 618) (F 502) (ä 7) (å 3) (G 811) (H 860) (ç 4) (I 344) (J 539) (è 29) (K 656) (é 128) (ê 6) (L 912) (M 1,686) (N 531)

с (ascii): 0,0079 секунды

// $ gcc -O3 cc_ascii.c -o cc_ascii && time -p ./cc_ascii < input.txt
#include <stdio.h>

enum { N = 256 };
size_t counts[N];

int main(void) {
  // count characters
  int ch = -1;
  while((ch = getchar()) != EOF)
    ++counts[ch];
  
  // print result
  size_t i = 0;
  for (; i < N; ++i) 
    if (counts[i])
      printf("('%c' %zu) ", (int)i, counts[i]);
  return 0;
}
person jfs    schedule 26.03.2010
comment
+1: Вау! это довольно тщательное сравнение с интересными подходами! - person Eric O Lebigot; 27.03.2010
comment
@Дж.Ф. Я также опубликовал свое расширение C Char Counter, не могли бы вы также включить/протестировать свой тест? stackoverflow.com/questions/2522152/ - person YOU; 28.03.2010
comment
@S.Mark: я сравнил вариант на основе «numpy» и ваш (я назвал его «smark»). Полные программы имеют одинаковое время для «американо-английского» (90 мс). Но профилировщик показывает, что 'smark' в 4 раза быстрее, чем 'numpy' (только счетная часть без загрузки файла и преобразования его в юникодные части). Для big.txt: 'numpy' - 170 мс, 'smark' - 130 мс, cc_ascii(fgets) - 30 мс ('numpy' в 3 раза медленнее, чем 'smark', если не учитывать чтение, декодирование файла). - person jfs; 28.03.2010
comment
@Дж.Ф. , Спасибо, что включили мою и отличную сравнительную таблицу. Я думаю, что если вы переместите char_counter и char_list в переменную уровня модуля и определите местонахождение в PyMODINIT_FUNC (это однократное выделение при импорте модуля) и скомпилируете с помощью gcc -O3, я думаю, это может получить скорость уровня cc_ascii. - person YOU; 29.03.2010
comment
@S.Mark: статические переменные в C инициализируются только один раз. Взгляните на gist.github.com/347279 Нет смысла перемещать char_counter и char_list в глобальную уровень. - person jfs; 29.03.2010
comment
@Дж.Ф. Ах, хорошо, вы правы, я пропустил !char_counter && часть, которую вы добавили. - person YOU; 29.03.2010
comment
@S.Mark: кстати, флаг -O3 не действует (те же 0,13 секунды для big.txt). Я построил расширение с помощью команды: $ CFLAGS=-O3 python setup.py build --force. - person jfs; 30.03.2010
comment
@Дж.Ф. Понятно. Итак, единственная разница сейчас в том, что я не учитывал время для malloc в своем тесте и различии ОС (я тестировал в Windows, но я не думаю, что Windows по какой-либо причине работает быстрее для C-кодов) . В любом случае, я думаю, что это достаточно быстро, и спасибо за множество тестов, ура! - person YOU; 30.03.2010
comment
разве counts не должна быть локальной переменной? - person Elazar; 25.06.2013
comment
В примере с Cython, в countchars_cython, где вы выполняете for i in range(0x10000): L[i] = 0, я бы сделал: L = [0]*0x10000 Это работает для Cython? - person Aaron Hall; 12.05.2014
comment
@AaronHall: да, вы можете использовать [0]*0x10000 в Cython, потому что это (почти) надмножество Python. Нет, вы не можете использовать его в этом случае, потому что L — это массив C (не L-значение). - person jfs; 12.05.2014

Как насчет того, чтобы избежать операций с плавающей запятой внутри цикла и сделать это после того, как все будет сделано?

Таким образом, вы можете просто делать +1 каждый раз, и это должно быть быстрее.

А лучше используйте collections.defaultdict, как советовал С.Лотт.

freqs=collections.defaultdict(int)

for char in text: 
   freqs[char]+=1

Или вы можете попробовать collections.Counter в python 2.7+

>>> collections.Counter("xyzabcxyz")
Counter({'y': 2, 'x': 2, 'z': 2, 'a': 1, 'c': 1, 'b': 1})

Or

Вы можете попробовать psyco, которые выполняют компиляцию точно в срок для Python. У вас есть циклы, поэтому я думаю, что вы получите некоторый прирост производительности с помощью psyco.


Редактировать 1:

Я сделал несколько тестов на основе big.txt (~6,5 МБ), который используется в корректор орфографии автора Питер Норвиг

Text Length: 6488666

dict.get : 11.9060001373 s
93 chars {u' ': 1036511, u'$': 110, u'(': 1748, u',': 77675, u'0': 3064, u'4': 2417, u'8': 2527, u'<': 2, u'@': 8, ....

if char in dict : 9.71799993515 s
93 chars {u' ': 1036511, u'$': 110, u'(': 1748, u',': 77675, u'0': 3064, u'4': 2417, u'8': 2527, u'<': 2, u'@': 8, ....

dict try/catch : 7.35899996758 s
93 chars {u' ': 1036511, u'$': 110, u'(': 1748, u',': 77675, u'0': 3064, u'4': 2417, u'8': 2527, u'<': 2, u'@': 8, ....

collections.default : 7.29699993134 s
93 chars defaultdict(<type 'int'>, {u' ': 1036511, u'$': 110, u'(': 1748, u',': 77675, u'0': 3064, u'4': 2417, u'8': 2527, u'<': 2, u'@': 8, ....

Технические характеристики процессора: процессор Intel Mobile Atom с тактовой частотой 1,6 ГГц

Соответственно, dict.get самый медленный и collections.defaultdict самый быстрый, try/except тоже самый быстрый.


Редактировать 2:

Добавлены тесты collections.Counter, они медленнее, чем dict.get, и на моем ноутбуке это заняло 15 секунд.

collections.Counter : 15.3439998627 s
93 chars Counter({u' ': 1036511, u'e': 628234, u't': 444459, u'a': 395872, u'o': 382683, u'n': 362397, u'i': 348464,
person Community    schedule 26.03.2010
comment
Очень хороший совет! Это также должно повысить числовую точность. - person psihodelia; 26.03.2010
comment
Пожалуйста, используйте collections.defaultdict вместо этого. - person S.Lott; 26.03.2010
comment
Вместо лямбда-выражения вы можете использовать collections.defaultdict(int) для инициализации частот. - person Peter Hoffmann; 26.03.2010
comment
Возможно, было бы неплохо, если бы вы вообще убрали пример с голыми исключениями. Это очень плохой способ делать что-то почти (это почти 99,99%) во всех случаях. - person shylent; 26.03.2010
comment
collections.Counter на самом деле довольно медленный - person SilentGhost; 26.03.2010
comment
@SilentGhost, понятно, я никогда не делал для этого тест. может быть, они реализуют это на чистом питоне. - person YOU; 26.03.2010
comment
Я провел различные тесты на больших (МБ текста) файлах, и время не уменьшается, если я использую коллекции и целые числа вместо try/except с числами с плавающей запятой. - person psihodelia; 26.03.2010
comment
@psihodelia, я думаю, что try/except в python может быть сильно оптимизирован, поэтому вы не видите никакой разницы. как насчет того, чтобы попробовать psyco, он должен выполнять компиляцию точно в срок для ваших циклических кодов. - person YOU; 26.03.2010
comment
Я опубликовал решение Python на основе numpy с поддержкой Unicode. Обработка big.txt занимает 0,17 секунды (по сравнению с 0,07 секунды для простой версии ascii C и 2,72 секунды для collections.Counter). stackoverflow.com/questions/2522152/ - person jfs; 28.03.2010
comment
@Дж.Ф. Это отличные ориентиры. На самом деле я тоже писал C Char Counter Extentions для Python. Плохо, что у нас нет предупреждений о новых ответах и ​​изменениях в SO. +1 к твоему кстати. - person YOU; 28.03.2010
comment
Я добавил расширение C Char Counter Extension в отдельный ответ вместо добавления этого. stackoverflow.com/questions/2522152/ - person YOU; 28.03.2010
comment
@shylent: В try: d[key]+=1 \n except KeyError: \n d[key] = 1 нет ничего плохого. Его легко понять, и он быстрый, если имеется небольшое количество разных ключей по сравнению с общим количеством элементов, например, как в big.txt соотношение составляет ~ 100/1e6. Учитывая, что цикл for реализован с использованием исключения StopIteration, не так уж и плохо использовать try/except. - person jfs; 30.03.2010
comment
@Дж.Ф. Себастьян: Я говорил конкретно о голом за исключением, то есть за исключением оператора, который захватывает все (я думал, что сделал это довольно ясно). Для этого существует крайне мало допустимых вариантов использования. - person shylent; 31.03.2010

Я написал расширение Char Counter C для Python, оно выглядит в 300 раз быстрее, чем collections.Counter, и в 150 раз быстрее, чем collections.default(int)

C Char Counter : 0.0469999313354 s
93 chars {u' ': 1036511, u'$': 110, u'(': 1748, u',': 77675, u'0': 3064, u'4': 2417, u'8': 2527, u'<': 2, u'@': 8,

Вот коды расширения Char Counter C

static PyObject *
CharCounter(PyObject *self, PyObject *args, PyObject *keywds)
{
    wchar_t *t1;unsigned l1=0;

    if (!PyArg_ParseTuple(args,"u#",&t1,&l1)) return NULL;

    PyObject *resultList,*itemTuple;

    for(unsigned i=0;i<=0xffff;i++)char_counter[i]=0;

    unsigned chlen=0;

    for(unsigned i=0;i<l1;i++){
        if(char_counter[t1[i]]==0)char_list[chlen++]=t1[i];
        char_counter[t1[i]]++;
    }

    resultList = PyList_New(0);

    for(unsigned i=0;i<chlen;i++){
        itemTuple = PyTuple_New(2);

        PyTuple_SetItem(itemTuple, 0,PyUnicode_FromWideChar(&char_list[i],1));
        PyTuple_SetItem(itemTuple, 1,PyInt_FromLong(char_counter[char_list[i]]));

        PyList_Append(resultList, itemTuple);
        Py_DECREF(itemTuple);

    };

    return resultList;
}

Где char_counter и char_list распределены на уровне модуля, поэтому нет необходимости использовать malloc каждый раз при вызове функции.

char_counter=(unsigned*)malloc(sizeof(unsigned)*0x10000);
char_list=(wchar_t*)malloc(sizeof(wchar_t)*0x10000);

Он возвращает список с кортежами

[(u'T', 16282), (u'h', 287323), (u'e', 628234), (u' ', 1036511), (u'P', 8946), (u'r', 303977), (u'o', 382683), ...

Для преобразования в формат dict достаточно dict().

dict(CharCounter(text))

PS: Бенчмарк включал время преобразования в dict

CharCounter принимать только Python Unicode String u"", если текст utf8, необходимо сделать .decode("utf8") заранее.

Ввод поддерживает Unicode до Basic Multilingual Plane (BMP) — от 0x0000 до 0xFFFF

person YOU    schedule 28.03.2010
comment
Я опубликовал обновленное сравнение (включая ваше расширение) stackoverflow.com/questions/2522152/ - person jfs; 29.03.2010
comment
Я добавил к сравнению расширение Python, написанное на Cython. Это в 2 раза быстрее, чем написанное от руки расширение на C. character/2525617#2525617" title="python - это словарь, который медленно находит частоту каждого символа"> stackoverflow.com/questions/2522152/ - person jfs; 23.01.2011

Нет, это не самый быстрый, потому что вы знаете, что символы имеют ограниченный диапазон, и вы можете использовать список и прямую индексацию, используя числовое представление символа, для хранения частот.

person Tuomas Pelkonen    schedule 26.03.2010
comment
Ты прав. Это должно дать небольшой аванс (без проверок try/except). - person psihodelia; 26.03.2010
comment
Да, диапазон ограничен 2**21 возможностью. - person John Machin; 26.03.2010

Очень, очень трудно победить dict. Он очень хорошо настроен, так как почти все в Python основано на словах.

person Ignacio Vazquez-Abrams    schedule 26.03.2010
comment
Настроен? Словарь — это хеш, а поиск — O(1). Настройка не имеет большого значения, когда у вас есть алгоритм, который по сути такой быстрый. - person S.Lott; 26.03.2010
comment
Сам алгоритм хеширования был настроен для большинства встроенных типов, учитывая, что ключ может быть любого хешируемого типа. - person Ignacio Vazquez-Abrams; 26.03.2010
comment
@S.Lott: Теоретически имеет значение только асимптотическая производительность. На практике также имеют значение постоянные коэффициенты, скрытые за нотацией большого O. - person Daniel Stutzbach; 26.03.2010
comment
@Daniel Stutsbach Philosoraptor соглашается. - person orokusaki; 26.03.2010

Я не знаком с python, но для поиска частот, если вы не знаете диапазон частот (в этом случае вы можете использовать массив), словарь - это то, что вам нужно.
Если вы знаете свои символы в юникоде, ASCII и т. д., вы можете определить массив с правильным количеством значений.
Однако это изменит пространственную сложность этого с O (n) до O (возможно n), но вы заработаете временную сложность улучшение с O(n*(время извлечения/вставки словаря)) до O(n).

person Rubys    schedule 26.03.2010
comment
Хотя я предложил тот же ответ, большая буква О такая же :-) - person fortran; 26.03.2010

Если вас действительно беспокоит скорость, вы можете сначала подсчитать символы с целыми числами, а затем получить частоты с помощью деления (с плавающей запятой).

Вот цифры:

python -mtimeit -s'x=0' 'x+=1'      
10000000 loops, best of 3: 0.0661 usec per loop

python -mtimeit -s'x=0.' 'x+=1.'
10000000 loops, best of 3: 0.0965 usec per loop
person Eric O Lebigot    schedule 26.03.2010

ну, вы можете сделать это в старомодном стиле... поскольку мы знаем, что разных символов не так уж много, и они непрерывны, мы можем использовать простой массив (или список здесь) и использовать порядковую нумерацию символов для индексации:

s = 1.0*len(text)
counts = [0]*256 # change this if working with unicode
for char in text: 
    freqs[ord(char)]+=1

freqs = dict((chr(i), v/s) for i,v in enumerate(counts) if v)

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

person fortran    schedule 26.03.2010
comment
Вариант на основе list в 2 раза медленнее, чем вариант try/except (проверено на big.txt). - person jfs; 28.03.2010

Используя этот код в «Алисе в стране чудес» (163793 символа) и «Библии, версия Дуэ-Реймса» (5649295 символов) из Project Gutenberg:

from collections import defaultdict
import timeit

def countchars():
    f = open('8300-8.txt', 'rb')
    #f = open('11.txt')
    s = f.read()
    f.close()
    charDict = defaultdict(int)
    for aChar in s:
        charDict[aChar] += 1


if __name__ == '__main__':
    tm = timeit.Timer('countchars()', 'from countchars import countchars')  
    print tm.timeit(10)

Я получил:

2.27324003315 #Alice in Wonderland
74.8686217403 #Bible

Соотношение между количеством символов для обеих книг равно 0.029, а соотношение между временем равно 0.030, поэтому алгоритм равен O(n) с очень небольшим постоянным коэффициентом. Думаю, достаточно быстро для большинства (всех?) целей.

person Chinmay Kanchi    schedule 26.03.2010
comment
Вы defaultdict могли бы просто быть defaultdict(int). Нет необходимости в defaultFactory(). - person Eric O Lebigot; 26.03.2010
comment
Фиксированный. Я не знал, что int() возвращает 0. - person Chinmay Kanchi; 26.03.2010

Если данные находятся в однобайтовой кодировке, вы можете использовать numpy для ускорения процесса подсчета:

import numpy as np

def char_freq(data):
    counts = np.bincount(np.frombuffer(data, dtype=np.byte))
    freqs = counts.astype(np.double) / len(data)
    return dict((chr(idx), freq) for idx, freq in enumerate(freqs) if freq > 0)

Некоторые быстрые тесты показывают, что это примерно в 10 раз быстрее, чем агрегирование в defaultdict(int).

person Ants Aasma    schedule 26.03.2010
comment
Я опубликовал решение на основе numpy, которое поддерживает юникод 2525617#2525617" title="python - это словарь, который медленно находит частоту каждого символа"> stackoverflow.com/questions/2522152/ - person jfs; 28.03.2010
comment
Этот ответ занимает первое место в рейтинге, разве голоса не должны быть выше? - person Aaron Hall; 12.05.2014

Чтобы избежать попытки, кроме накладных расходов, вы можете использовать defaultdict.

person Xavier Combelle    schedule 26.03.2010

Небольшое ускорение даст использование метода dict.setdefault, таким образом вы не платить немаленькую цену за каждого нового встречающегося персонажа:

for char in text:
    freq[char] = freq.setdefault(char, 0.0) + P

В качестве примечания: иметь голые except: считается очень плохой практикой.

person Łukasz    schedule 26.03.2010
comment
Пожалуйста, используйте collections.defaultdict вместо этого. Это еще проще и быстрее. Кроме того, с плавающей запятой в вопросе действительно плохо. - person S.Lott; 26.03.2010
comment
setdefault почти в 3 раза медленнее, чем вариант try/except, протестированный на файле big.txt. - person jfs; 28.03.2010