索引引擎的基本工作原理便是倒排索引, 即将一个文档所包含的文字反过来映射至文档; 这方面算法并没有太多花样可言, 为了增加效率, 索引数据尽可往内存里面搬, 此法可效王献之习书法之势, 只要把十八台机器内存全部塞满, 那么基本也就功成名就了. 而基本思路举个简单例子, 现在有以下文档 (分词已经完成) 以及其包含的关键词
- doc_a: [word_w, word_x, word_y]
- doc_b: [word_x, word_z]
- doc_c: [word_y]
- word_w -> [doc_a]
- word_x -> [doc_a, doc_b]
- word_y -> [doc_a, doc_c]
- word_z -> [doc_b]
doc_a = {'id': 'a', 'words': ['word_w', 'word_x', 'word_y']}
doc_b = {'id': 'b', 'words': ['word_x', 'word_z']}
doc_c = {'id': 'c', 'words': ['word_y']}
docs = [doc_a, doc_b, doc_c]
indices = dict()
for doc in docs:
for word in doc['words']:
if word not in indices:
indices[word] = []
indices[word].append(doc['id'])
print indices
if word not in indices:
indices[word] = []
dict
的 setdefault(key, default=None)
接口替换. 此接口的作用是, 如果 key
在字典里, 那么好说, 拿出对应的值来; 否则, 新建此 key
, 且设置默认对应值为 default
. 但从设计上来说, 我不明白为何 default
有个默认值 None
, 看起来并无多大意义, 如果确要使用此接口, 大体都会自带默认值吧, 如下for doc in docs:
for word in doc['words']:
indices.setdefault(word, []).append(doc['id'])
不过在某些情况下,
setdefault
用起来并不顺手: 当 default
值构造很复杂时, 或产生 default
值有副作用时, 以及一个之后会说到的情况; 前两种情况一言以蔽之, 就是 setdefault
不适用于 default
需要惰性求值的场景. 换言之, 为了兼顾这种需求, setdefault
可能会设计成def setdefault(self, key, default_factory):
if key not in self:
self[key] = default_factory()
return self[key]
for doc in docs:
for word in doc['words']:
indices.setdefault(word, list).append(doc['id'])
如果说上面只是一个能预见但实际上可能根本不会遇到的 API 缺陷, 那么下面这个就略打脸了.
考虑现在要进行词频统计, 即一个词在文章中出现了多少次, 如果直接拿
dict
来写, 大致是def word_count(words):
count = dict()
for word in words:
count.setdefault(word, 0) += 1
return count
print word_count(['hiiragi', 'kagami', 'hiiragi', 'tukasa', 'yosimizu', 'kagami'])
+=
操作符左边的 count.setdefault(word, 0)
在 Python 中不是一个左值. 怎样, 现在开始念叨 C艹 类型体系的好了吧.因为 Python 把默认的字面常量
{}
等价于 dict()
就认为 dict
是银弹的思想是要不得的; Python 里面各种数据结构不少, 解决统计问题, 理想的方案是 collections.defaultdict
这个类. 下面的代码想必看一眼就明白from collections import defaultdict
doc_a = {'id': 'a', 'words': ['word_w', 'word_x', 'word_y']}
doc_b = {'id': 'b', 'words': ['word_x', 'word_z']}
doc_c = {'id': 'c', 'words': ['word_y']}
docs = [doc_a, doc_b, doc_c]
indices = defaultdict(list)
for doc in docs:
for word in doc['words']:
indices[word].append(doc['id'])
print indices
def word_count(words):
count = defaultdict(int)
for word in words:
count[word] += 1
return count
print word_count(['hiiragi', 'kagami', 'hiiragi', 'tukasa', 'yosimizu', 'kagami'])
此外
collections
里还有个 Counter
, 可以粗略认为它是 defaultdict(int)
的扩展.