Некоторые измерения. Я взял 10 МБ бесплатного текста электронной книги и вычисленные частоты триграмм, получив файл размером 24 МБ. Хранение его в разных простых структурах данных Python заняло столько места в килобайтах, измеряемое как RSS от числа запущенных ps, где d - это dict, ключи и freqs - это списки, а a, b, c, freq - поля записи триграммы:
295760 S. Lott's answer
237984 S. Lott's with keys interned before passing in
203172 [*] d[(a,b,c)] = int(freq)
203156 d[a][b][c] = int(freq)
189132 keys.append((a,b,c)); freqs.append(int(freq))
146132 d[intern(a),intern(b)][intern(c)] = int(freq)
145408 d[intern(a)][intern(b)][intern(c)] = int(freq)
83888 [*] d[a+' '+b+' '+c] = int(freq)
82776 [*] d[(intern(a),intern(b),intern(c))] = int(freq)
68756 keys.append((intern(a),intern(b),intern(c))); freqs.append(int(freq))
60320 keys.append(a+' '+b+' '+c); freqs.append(int(freq))
50556 pair array
48320 squeezed pair array
33024 squeezed single array
Записи, помеченные [*], не имеют эффективного способа поиска пары (a, b); они перечислены только потому, что другие предложили их (или их варианты). (Я был в некотором роде раздражен, когда делал это, потому что ответы с наибольшим количеством голосов не помогли, как показано в таблице.)
«Массив пар» - схема, приведенная ниже в моем исходном ответе («Я бы начал с массива с ключами».
будучи первыми двумя словами ... "), где таблица значений для каждой пары
представлены в виде одной строки. «Сжатый массив пар» - то же самое,
опуская значения частоты, равные 1 (наиболее распространенные
дело). «Squeezed single array» похож на массив сжатых пар, но объединяет ключ и значение в одну строку (с разделителем). Код сжатого одиночного массива:
import collections
def build(file):
pairs = collections.defaultdict(list)
for line in file: # N.B. file assumed to be already sorted
a, b, c, freq = line.split()
key = ' '.join((a, b))
pairs[key].append(c + ':' + freq if freq != '1' else c)
out = open('squeezedsinglearrayfile', 'w')
for key in sorted(pairs.keys()):
out.write('%s|%s\n' % (key, ' '.join(pairs[key])))
def load():
return open('squeezedsinglearrayfile').readlines()
if __name__ == '__main__':
build(open('freqs'))
Я не написал код для поиска значений из этой структуры (используйте bisect, как упомянуто ниже) или не реализовал более сложные сжатые структуры, также описанные ниже.
Оригинальный ответ: Простой отсортированный массив строк, каждая строка представляет собой разделенную пробелами конкатенацию слов, поиск которых выполняется с помощью модуля bisect, стоит попробовать для начала. Это экономит место на указателях и т. Д. Это по-прежнему тратит пространство из-за повторения слов; есть стандартный прием для удаления общих префиксов, с другим уровнем индекса для их возврата, но это довольно сложно и медленнее. (Идея состоит в том, чтобы хранить последовательные фрагменты массива в сжатой форме, которую необходимо сканировать последовательно, вместе с индексом произвольного доступа к каждому фрагменту. Блоки достаточно большие для сжатия, но достаточно малы для разумного времени доступа. Особое сжатие здесь применима следующая схема: если последовательные записи - «привет, георгий» и «привет, мир», вместо этого сделайте вторую запись «6world» (6 - это общая длина префикса.) Или, возможно, вы можете избежать использования zlib ? В любом случае, вы можете узнать больше в этом ключе, просматривая словарные структуры, используемые в полнотекстовом поиске.) Поэтому, в частности, я бы начал с массива с ключами, являющимися первыми двумя словами, с параллелью массив, в записях которого перечислены возможные третьи слова и их частоты. Это может все еще быть отстойным - я думаю, что вам может не повезти, если в комплект входят батарейки с эффективным энергопотреблением.
Кроме того, двоичные древовидные структуры не рекомендуются для эффективности памяти здесь. Например, в этой статье проверяется множество структур данных по аналогичной проблеме (хотя вместо триграмм - униграммы) и находит хеш-таблицу, которая превосходит все древовидные структуры по этой мере.
Я должен был упомянуть, как и кто-то другой, что отсортированный массив можно использовать только для списка слов, а не для биграмм или триграмм; затем для вашей «реальной» структуры данных, какой бы она ни была, вы используете целочисленные ключи вместо строк - индексы в списке слов. (Но это удерживает вас от использования общих префиксов, за исключением самого списка слов. Может быть, мне не стоит предлагать это в конце концов.)