汉字分词最简单的就是正向最大匹配分词了,其基本原理很简单,而且经常作为笔试题
该算法主要分两个步骤:
1 一般从一个字符串的开始位置,选择一个最大长度的词长的片段,如果序列不足最大词长,则选择全部序列。
2 首先看该片段是否在词典中,如果是,则算为一个分出来的词,如果不是,则从右边开始,减少一个字符,然后看短一点的这个片段是否在词典中,依次循环,逐到只剩下一个字。
3 序列变为第2步骤截取分词后,剩下的部分序列。
然后就开始写代码了,这里使用英文进行分词,如“itisatest”,需要分为“it is a test”,词典文件为count_1w.txt,数据来源于beautiful data(数据之美)这本书,附件中有。
之所以经常会面试写这个,主要是这个是个典型的应用分治思路的题目,只要分清楚了,代码其实非常简单易懂,如果几个步骤混在一起考虑,就非常容易糊涂。
对于这样稍微有点逻辑算法的题目,一般是思路就是case方法,就是用一个例子,看下算法怎么跑,然后再分析终止条件和一些特殊情况。当然,标准的设计初始条件,找出循环不变式的方法也是可以的,但是太麻烦了。。。。。。。。。。,一般都是先把代码写出来了,再来证明代码的正确性@#¥#@¥
画个简单的例子图,这样写就容易的多,人的思维很难同时思考很多情况很多步骤,case方法是最直接的:
这里就是注意,黑点的位置就是字母的索引下标,就是唯一字母的前一个空位,这样理解更清楚一些。特别是做分词,黑点就是所有可能的分割位置。
有了这个case,就是分别写两个步骤的处理函数,然后主函数设置初始条件,并循环,调用两个函数,并判断终止条件即可。
具体代码如下(python):
#!/usr/bin/env python
#coding=utf-8
#############################################################
#function: 最大正向分词
##############################################################
import sys
import math
#global parameter
DELIMITER = " " #分词之后的分隔符
class MLSegment:
def __init__(self):
self.word_dict = {} #记录单词
self.gmax_word_length = 0 #单词的最大长度
#功能:从sequence中选取一个片段,
# 如果sequence长度大于最大词长,则从头取最大词长的片段
# 否则,则取整个sequence作为片段
#输入:sequence 需要处理的序列
#返回:(选择的片段,剩下的片段)
def get_segment(self, sequence):
if len(sequence) > self.gmax_word_length:
return (sequence[0:self.gmax_word_length], \
sequence[self.gmax_word_length:])
else:
return (sequence,"")
#功能:从sequence选择一个最长的词
# 基本方法,先取整个sequence,看是否是词,如果不是,依次从右边
# 减少字母,看缩短的片段是否是词,如果减少到只剩下一个字母,则直接
# 作为一个词
#返回:(词,剩下的片段)
def select_max_length_word(self, sequence):
for length in range(len(sequence),1,-1):
word = sequence[0:length]
if self.word_dict.has_key(word):
return (word, sequence[length:] )
#至少分出一个字母
return (sequence[0:1],sequence[1:])
#最大长度分词
def ml_seg(self, sequence):
sequence = sequence.strip()
#初始化
segment_start_pos = 0
leave_segment = sequence
word_list = []
#开始循环
while True:
#step 1,获取一段片段,用来找词
(select_segment, leave_segment) = self.get_segment(leave_segment)
#step 2, 从选择的片段中获得可能的最长的词,以及剩余的部分片段
(word,word_right) = self.select_max_length_word(select_segment)
word_list.append(word)
#step 3,将剩余的词片段与剩下的片段组合,重新进入下一轮循环
leave_segment = word_right + leave_segment
#如果剩下的部分为空,退出循环
if leave_segment == "":
break
return DELIMITER.join(word_list)
#加载词典,为词\t词频的格式
def initial_dict(self,filename):
dict_file = open(filename, "r")
for line in dict_file:
sequence = line.strip()
key = sequence.split('\t')[0]
self.word_dict[key] = 1
#获取最大词长
self.gmax_word_length = max(len(key) for key in self.word_dict.iterkeys())
#测试
if __name__=='__main__':
myseg = MLSegment()
myseg.initial_dict("count_1w.txt")
sequence = "itisatest"
seg_sequence = myseg.ml_seg(sequence)
print "original sequence: " + sequence
print "segment result: " + seg_sequence
这个是完全按照上面的思路写的,当然,因为截取片段的时候,其实只要记录截取的起始位置即可,然后逐步更新这个变量就行,不用记录所有的片段字符串,因此可以写一个改进方法,代码比上面的更清楚,但是理解起来会稍微麻烦一些。写代码一般都是从基础思路写起,然后慢慢改进的,不能一下就找出最优的写法:
#!/usr/bin/env python
#coding=utf-8
#############################################################
#function: 正向最大分词
##############################################################
import sys
import math
#global parameter
DELIMITER = " " #分词之后的分隔符
class MLSegment:
def __init__(self):
self.word_dict = {} #记录单词
self.gmax_word_length = 0 #单词的最大长度
#最大长度分词
def ml_seg(self, sequence):
sequence = sequence.strip()
#初始化
segment_start_pos = 0 #开始
word_list = []
#循环体
while True:
#step 1,确定候选片段的最大长度
if len(sequence) - segment_start_pos > self.gmax_word_length :
max_segment_length = self.gmax_word_length
else:
max_segment_length = len(sequence) - segment_start_pos
#step 2,从候选片段中选择候选词
for word_length in range(max_segment_length, 0, -1):
word = sequence[segment_start_pos:segment_start_pos+word_length]
if self.word_dict.has_key(word):
word_list.append(word)
break
if 1 == word_length and not self.word_dict.has_key(word):
word_list.append(word)
#候选片段位置前移
segment_start_pos += word_length
#如果移动到了末尾位置,则退出
if segment_start_pos == len(sequence):
break
return DELIMITER.join(word_list)
#加载词典,为词\t词频的格式
def initial_dict(self,filename):
dict_file = open(filename, "r")
for line in dict_file:
sequence = line.strip()
key = sequence.split('\t')[0]
self.word_dict[key] = 1
#获取最大词长
self.gmax_word_length = max(len(key) for key in self.word_dict.iterkeys())
#测试
if __name__=='__main__':
myseg = MLSegment()
myseg.initial_dict("count_1w.txt")
sequence = "itisatest"
seg_sequence = myseg.ml_seg(sequence)
print "original sequence: " + sequence
print "segment result: " + seg_sequence
当然,还可以进一步的写递归的形式,会更简洁,但递归基本都是奇淫技巧,实际项目很少用到。
对"itisatest", 利用正向最大匹配,分割出来的是“itis atest”, 现在不符合需求,但是一个最基本的分词方法,在汉语分词上也有不错的效果,一般都作为分词算法的baseline。
后面一节就介绍最大概率分词,就可以解决这个问题。
字典文件和代码baidupan下载:http://pan.baidu.com/s/1dDikxg9