一些度量。我取了10MB的免费电子书文本并计算了三元组频率,生成一个24MB的文件。将其存储在不同的简单Python数据结构中所占用的空间大小以kB为单位进行测量,通过运行ps来测量RSS,其中d是一个字典,keys和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)的方法;它们只是列出来是因为其他人建议过它们(或者是它们的变体)。 (正如表格所示,我有点生气,才写了这篇文章。)
“Pair array”是我的原始答案中下面的方案(“我会从键为前两个单词的数组开始……”),其中每个对的值表现为一个单独的字符串。“Squeezed pair array”与此相同,省略了等于1的频率值(最常见的情况)。 “Squeezed single array”类似于“squeezed pair array”,但将键和值作为一个字符串粘合在一起(使用分隔符字符)。压缩后的单个数组代码:
import collections
def build(file):
pairs = collections.defaultdict(list)
for line in file:
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 模块。这可以节省指针等空间。但它仍然浪费了空间,因为单词会重复;有一种标准技巧可以剥离公共前缀并使用另一级索引将它们还原,但这更加复杂和缓慢。(思路是将数组的连续块以压缩形式存储,必须按顺序扫描,以及每个块的随机访问索引。块足够大以进行压缩,但访问时间合理。适用于此处的特定压缩方案:如果连续条目是“hello george”和“hello world”,则使第二个条目成为“6world”。 (6 是相同前缀的长度)。或者也许您可以使用 zlib?无论如何,您可以通过查找全文搜索中使用的字典结构了解更多信息。)因此,具体而言,我会从数组开始,键是前两个单词,并有一个并行的数组,其条目列出了可能的第三个单词及其频率。但它可能仍然不太好 - 我认为您在内存高效选项上可能运气不佳。
此外,二叉树结构在这里不推荐使用。例如,这篇论文 在类似问题上测试了各种数据结构(虽然是 unigrams 而不是 trigrams),并发现哈希表在该度量标准下都优于所有树结构。
我应该提到,正如其他人所做的那样,排序数组可以仅用于单词列表,而不是 bigrams 或 trigrams;然后对于您的“真实”数据结构,无论它是什么,都使用整数键而不是字符串 - 这是单词列表中的索引。(但这使您无法在单词列表之外利用公共前缀。也许我不应该建议这样做。)