倒排索引统计与 Python 字典

    最近折腾索引引擎以及数据统计方面的工做比较多, 与 Python 字典频繁打交道, 至此整理一份此方面 API 的用法与坑法备案.
    索引引擎的基本工做原理即是倒排索引, 即将一个文档所包含的文字反过来映射至文档; 这方面算法并无太多花样可言, 为了增长效率, 索引数据尽可往内存里面搬, 此法可效王献之习书法之势, 只要把十八台机器内存所有塞满, 那么基本也就功成名就了. 而基本思路举个简单例子, 如今有如下文档 (分词已经完成) 以及其包含的关键词html

  • doc_a: [word_w, word_x, word_y]
  • doc_b: [word_x, word_z]
  • doc_c: [word_y]

将其变换为python

  • word_w -> [doc_a]
  • word_x -> [doc_a, doc_b]
  • word_y -> [doc_a, doc_c]
  • word_z -> [doc_b]

    写成 Python 代码, 即是面试

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  可能会设计成app

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  来写, 大体是spa

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  这个类. 下面的代码想必看一眼就明白.net

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)  的扩展.

推荐阅读:

[1] 关于腾讯的一道字符串匹配的面试题

http://my.oschina.net/leejun2005/blog/78738

[2] 搜索引擎:该如何设计你的倒排索引?

http://mp.weixin.qq.com/s/aTMoiNPTatFLq2abLsGFiQ

相关文章
相关标签/搜索