巧用Python字典处理索引统计

索引引擎的基本工作原理便是倒排索引,即将一个文档所包含的文字反过来映射至文档;这方面算法并没有太多花样可言,为了增加效率,索引数据尽可往内存里面搬。

而基本思路举个简单例子,现在有以下文档 (分词已经完成) 以及其包含的关键词:

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]

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

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

本文转自:https://www.py.cn/jishu/gaoji/10394.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值