Long Time No See...
最近深受痛苦的折磨,这一年来所有的事跌宕起伏,如同一瞬,一个个打击接踵而至,从年初的各种擦边挂,到各种失败,各种放弃,似乎没有发生一个顺心的事,不知道从什么时候起戾气变得越来越重,更无与人说。不管如何,“尽吾志也而不能至者,可以无悔矣,其孰能讥之乎?”……
我决定重拾包袱,从最初开始,从现在开始……
因为考研耽误了好多事,包括友谊,包括学习,在这就边做实验边复习点这学期的知识。
词法分析:把字符串序列转化为单词序列的过程,进行词法分析的函数或程序叫作词法分析器或扫描器。
例如:“XXXXXXXXXXX”->"X/XX/X/XX/X/X/X/X/X"
中文分词技术:把一句话根据词语字典切分为若干个词语。方法大体分为三类:字符匹配法、理解法、统计法,字符匹配法包括正向最大匹配、逆向最大匹配、最少切分、双向最大匹配等。
本次任务:给定字典,[‘研究’,’研究生’, ’生命’,’命’,’的’,‘起源’],使用正向最大匹配(Maximum Match Method,MM)和逆向最大匹配(Reverse Maximum Match Method,RMM)对“研究生命的起源”进行切分。
MM:
Step 1 假定分词词典中的最长词有i个汉字字符,则用被处理的当前字串中的前i个字作为匹配字段,查找字典。
Step 2 若字典中存在这样一个i字词,则匹配成功;否则,失败,将匹配字段中的最后一个字符去掉, 对剩下字串进行匹配。 Step 3 如此进行下去,直到匹配成功,即切分出一个词或剩余字串长度为0。 不停的匹配,直到文档被扫描完为止。
编程思路:第一步求词典中最长分词的长度,设为maxlen。设cobegin为源字符串时匹配的开始字符下标。cobegin初始为0,设coend为字符串的末尾下标的下一个,即coend=len(s)
第二步,设切分的大小为size,初始化为maxlen,如果切下的s[cobegin:cobegin+size]在字典中,则把切下的放在一个列表中,并cogebin=cobegin+size 且size重置为maxlen,否则size减1,重新匹配,当size为0且cobegin<coend 时,则表明无法将切分完毕;当cobegin>=coend时,退出循环。
代码实现 MaxMatch.py
dic=['研究','研究生','生命','命','的','起源']
out=[]
s='研究生命的起源'
maxlen=0 #字典中最大分词长度
for i in dic:
if maxlen<len(i):
maxlen=len(i)
cobegin=0 #剩余字符开始
coend=len(s) #剩余字符结束
size=maxlen #开始匹配长度
while cobegin<coend:
ch=s[cobegin:cobegin+size]
if ch in dic:#匹配成功
cobegin+=size
out.append(ch)
size=maxlen
else: #匹配失败
size-=1
if size==0:
print("error")
break
最后结果:
好像不太对的样子,我意为自己写错了从网上复制了一个c++的代码(忘了在哪个博客找到的),运行,还是这个结果:
MM.cpp:
#include<iostream>
#include<string>
using namespace std;
// 宏,计算数组个数
#define GET_ARRAY_LEN(array,len){len=(sizeof(array)/sizeof(array[0]));}
string dict[] = {"研究", "研究生", "生命", "命", "的","起源"};
// 是否为词表中的词或者是词表中词的前缀
bool inDict(string str)
{
bool res = false;
int i;
int len = 0;
GET_ARRAY_LEN(dict, len);
for (i = 0; i<len; i++)
{
// 是否和词表词相等或者是词表词前缀
if( str == dict[i].substr(0, str.length()))
{
res = true;
}
}
return res;
}
int main()
{
string sentence = "研究生命的起源";
string word = "一";
int wordlen = word.length();
int i;
string s1 = "";
for (i = 0; i<sentence.length(); i = i+wordlen)
{
string tmp = s1 + sentence.substr(i, wordlen);
if(inDict(tmp))
{
s1 = s1 + sentence.substr(i, wordlen);
}
else
{
cout<<"分词结果:"<<s1<<endl;
s1 = sentence.substr(i, wordlen);
}
}
cout<<"分词结果:"<<s1<<endl;
}
结果还是一样:
RMM:原理和MM类似,唯一不同的时候匹配是倒着匹配(从字符串的最后开始),然后对最后切分的字段进行逆置即可。
ReMaxMatch.py:
import math
dic=['研究','研究生','生命','命','的','起源']
out=[]
s='研究生命的起源'
maxlen=0 #字典中最大分词长度
for i in dic:
if maxlen<len(i):
maxlen=len(i)
cobegin=0 #剩余字符开始
coend=len(s) #剩余字符结束
size=maxlen #开始匹配长度
while cobegin<coend:
ch=s[coend-size:coend]
if ch in dic:#匹配成功
coend-=size
out.append(ch)
size=maxlen
else: #匹配失败
size-=1
if size==0:
print("error")
break
i=0
while i<math.floor(len(out)/2): #逆置
str1=out[i]
out[i]=out[len(out)-i-1]
out[len(out)-i-1]=str1
i+=1
结果:
两者的差异:
正向匹配时会优先选择匹配长度更长的单词,但同一前缀的单词并不是越长就越正确,逆向同理会匹配同一后缀匹配长度更长的单词,两者都有其局限性,需要做出一定的优化,比如使用最小切分法和双向切分等。