求一个基于java的模糊匹配算法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了求一个基于java的模糊匹配算法相关的知识,希望对你有一定的参考价值。

参考技术A 今天因为业务需求,需要在java中进行字符串的模糊匹配,由于要比较的对象长度不同,也没有固定的组成规则,收集到下列三个方法解决问题
方法一、
public intindexOf(String str)
返回指定子字符串在此字符串中第一次出现处的索引。返回的整数是
this.startsWith(str, k)为 true 的最小 k值。
参数:str - 任意字符串。
返回:如果字符串参数作为一个子字符串在此对象中出现,则返回第一个这种子字符串的第一个字符的索引;如果它不作为一个子字符串出现,则返回-1。
if(str1.indexOf("RO")>=0 ||str1.indexOf("EL")>=0 ||str1.indexOf("RO")>=0)
存在
方法二、
public boolean contains(CharSequence s)
当且仅当此字符串包含指定的 char 值序列时,返回true。
参数:s- 要搜索的序列(注意:String 类是实现CharSequence接口的一个实体类)
返回:如果此字符串包含s,则返回 true,否则返回 false
例子:
Str.contains("12334")
注意:,意思为,Str字符串中出现字符串“12345”五个字符中的任何一个,那么则返回true;
方法三、通过正则表达式+matches方法
publicboolean matches(String regex)
告知此字符串是否匹配给定的正则表达式。

调用此方法的str.matches(regex)形式与以下表达式产生的结果完全相同:
Pattern.matches(regex,str)
参数:regex - 用来匹配此字符串的正则表达式
返回:当且仅当此字符串匹配给定的正则表达式时,返回 true

友情提示,要想真正的用好正则表达式,正确的理解元字符是最重要的事情。下表列出了所有的元字符和对它们的一个简短的描述。
元字符 描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“\n”匹配字符“n”。“\\n”匹配一个换行符。序列“\\”匹配“\”而“\(”则匹配“(”。
^ 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
$ 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
* 匹配前面的子表达式零次或多次。例如,zo*能匹配“z”以及“zoo”。*等价于0,。
+ 匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于1,。
? 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”。?等价于0,1。
n n是一个非负整数。匹配确定的n次。例如,“o2”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
n, n是一个非负整数。至少匹配n次。例如,“o2,”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o1,”等价于“o+”。“o0,”则等价于“o*”。
n,m m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o1,3”将匹配“fooooood”中的前三个o。“o0,1”等价于“o?”。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符(*,+,?,n,n,,n,m)后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o?”将匹配所有“o”。
.点 匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“(.|\n)”的模式。
(pattern) 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\(”或“\)”。
(?:pattern) 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
(?=pattern) 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?<=pattern) 反向肯定预查,与正向肯定预查类似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。
(?<!pattern) 反向否定预查,与正向否定预查类似,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。
x|y 匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。
[xyz] 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz] 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”。
[a-z] 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
\B 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d 匹配一个数字字符。等价于[0-9]。
\D 匹配一个非数字字符。等价于[^0-9]。
\f 匹配一个换页符。等价于\x0c和\cL。
\n 匹配一个换行符。等价于\x0a和\cJ。
\r 匹配一个回车符。等价于\x0d和\cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]。
\S 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于\x09和\cI。
\v 匹配一个垂直制表符。等价于\x0b和\cK。
\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\xn 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。
\num 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
\nm 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。
\nml 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\un 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。
注意:在普通的正则表达式中,15个具有特殊意义的元字符需要进行转义:
( [ ] ) \ ^ - $ | ? * + .
字符串精确匹配
public booleanequalsIgnoreCase(String anotherString)
将此 String 与另一个 String比较,不考虑大小写。如果两个字符串的长度相同,并且其中的相应字符都相等(忽略大小写),则认为这两个字符串是相等的。
在忽略大小写的情况下,如果下列至少一项为 true,则认为 c1 和c2 这两个字符相同。
这两个字符相同(使用 == 运算符进行比较)。
对每个字符应用方法 Character.toUpperCase(char) 生成相同的结果。
对每个字符应用方法 Character.toLowerCase(char) 生成相同的结果。
参数:
anotherString - 与此 String 进行比较的String。
返回:
如果参数不为 null,且这两个 String 相等(忽略大小写),则返回true;否则返回 false。

NLPN-Gram自然语言处理模糊匹配编辑距离

目录

  1. 1.基于N-Gram模型定义的字符串距离

    1. N-Gram在模糊匹配中的应用

    2. 利用N-Gram计算字符串间距离的Java实例

  2. 2.利用N-Gram模型评估语句是否合理

  3. 3.使用N-Gram模型时的数据平滑算法

  4. 4.A Final Word

  5. 5.推荐阅读和参考文献

N-Gram(有时也称为N元模型)是自然语言处理中一个非常重要的概念,通常在NLP中,人们基于一定的语料库,可以利用N-Gram来预计或者评估一个句子是否合理。另外一方面,N-Gram的另外一个作用是用来评估两个字符串之间的差异程度。这是模糊匹配中常用的一种手段。本文将从此开始,进而向读者展示N-Gram在自然语言处理中的各种powerful的应用。

  • 基于N-Gram模型定义的字符串距离

  • 利用N-Gram模型评估语句是否合理

  • 使用N-Gram模型时的数据平滑算法


基于N-Gram模型定义的字符串距离

在自然语言处理时,最常用也最基础的一个操作是就是“模式匹配”,或者称为“字符串查找”。而模式匹配(字符串查找)又分为精确匹配模糊匹配两种。

所谓精确匹配,大家应该并不陌生,比如我们要统计一篇文章中关键词 “information” 出现的次数,这时所使用的方法就是精确的模式匹配。这方面的算法也比较多,而且应该是计算机相关专业必修的基础课中都会涉及到的内容,例如KMP算法、BM算法和BMH算法等等。

另外一种匹配就是所谓的模糊匹配,它的应用也随处可见。例如,一般的文字处理软件(例如,Microsoft Word等)都会提供拼写检查功能。当你输入一个错误的单词,例如 “ informtaion” 时,系统会提示你是否要输入的词其实是 “information” 。将一个可能错拼单词映射到一个推荐的正确拼写上所采用的技术就是模糊匹配。

模糊匹配的关键在于如何衡量两个长得很像的单词(或字符串)之间的“差异”。这种差异通常又称为“距离”。这方面的具体算法有很多,例如基于编辑距离的概念,人们设计出了 Smith-Waterman 算法和Needleman-Wunsch 算法,其中后者还是历史上最早的应用动态规划思想设计的算法之一。现在Smith-Waterman 算法和Needleman-Wunsch 算法在生物信息学领域也有重要应用,研究人员常常用它们来计算两个DNA序列片段之间的“差异”(或称“距离”)。甚至于在LeetCode上也有一道“No.72 Edit Distance”,其本质就是在考察上述两种算法的实现。可见相关问题离我们并不遥远。

N-Gram在模糊匹配中的应用

事实上,笔者在新出版的《算法之美——隐匿在数据结构背后的原理》一书中已经详细介绍了包括Needleman-Wunsch算法、Smith-Waterman算法、N-Gram算法、Soundex算法、Phonix算法等在内的多种距离定义算法(或模糊匹配算法)。而今天为了引出N-Gram模型在NLP中的其他应用,我们首先来介绍一下如何利用N-Gram来定义字符串之间的距离。

我们除了可以定义两个字符串之间的编辑距离(通常利用Needleman-Wunsch算法或Smith-Waterman算法)之外,还可以定义它们之间的N-Gram距离。N-Gram(有时也称为N元模型)是自然语言处理中一个非常重要的概念。假设有一个字符串 ,那么该字符串的N-Gram就表示按长度 N 切分原词得到的词段,也就是  中所有长度为 N 的子字符串。设想如果有两个字符串,然后分别求它们的N-Gram,那么就可以从它们的共有子串的数量这个角度去定义两个字符串间的N-Gram距离。但是仅仅是简单地对共有子串进行计数显然也存在不足,这种方案显然忽略了两个字符串长度差异可能导致的问题。比如字符串 girl 和 girlfriend,二者所拥有的公共子串数量显然与 girl 和其自身所拥有的公共子串数量相等,但是我们并不能据此认为 girl 和girlfriend 是两个等同的匹配。

为了解决该问题,有学者便提出以非重复的N-Gram分词为基础来定义 N-Gram距离这一概念,可以用下面的公式来表述: 

|GN(s)|+|GN(t)|2×|GN(s)GN(t)|


此处, |GN(s)|是字符串 s的 N-Gram集合,N 值一般取2或者3。以 N = 2 为例对字符串Gorbachev和Gorbechyov进行分段,可得如下结果(我们用下画线标出了其中的公共子串)。 



结合上面的公式,即可算得两个字符串之间的距离是8 + 9 − 2 × 4 = 9。显然,字符串之间的距离越小,它们就越接近。当两个字符串完全相等的时候,它们之间的距离就是0。


利用N-Gram计算字符串间距离的Java实例

在《算法之美——隐匿在数据结构背后的原理》一书中,我们给出了在C++下实现的计算两个字符串间N-Gram距离的函数,鉴于全书代码已经在本博客中发布,这里不再重复列出。事实上,很多语言的函数库或者工具箱中都已经提供了封装好的计算 N-Gram 距离的函数,下面这个例子演示了在Java中使用N-Gram 距离的方法。

针对这个例子,这里需要说明的是:

  • 调用函数需要引用lucene的JAR包,我所使用的是lucene-suggest-5.0.0.jar

  • 前面我们所给出的算法计算所得为一个绝对性的距离分值。而Java中所给出的函数在此基础上进行了归一化,也就是说所得之结果是一个介于0~1之间的浮点数,即0的时候表示两个字符串完全不同,而1则表示两个字符串完全相同。


  
    
    
  
  1. import org.apache.lucene.search.spell.*;


  2. public class NGram_distance {


  3. public static void main(String[] args) {


  4. NGramDistance ng = new NGramDistance();

  5. float score1 = ng.getDistance("Gorbachev", "Gorbechyov");

  6. System.out.println(score1);

  7. float score2 = ng.getDistance("girl", "girlfriend");

  8. System.out.println(score2);

  9. }

  10. }

有兴趣的读者可以在引用相关JAR包之后在Eclipse中执行上述Java程序,你会发现,和我们预期的一样,字符串Gorbachev和Gorbechyov所得之距离评分较高(=0.7),说明二者很接近;而girl和girlfriend所得之距离评分并不高(=0.3999),说明二者并不很接近。


利用N-Gram模型评估语句是否合理

从现在开始,我们所讨论的N-Gram模型跟前面讲过N-Gram模型从外在来看已经大不相同,但是请注意它们内在的联系(或者说本质上它们仍然是统一的概念)。

为了引入N-Gram的这个应用,我们从几个例子开始。 
首先,从统计的角度来看,自然语言中的一个句子  可以由任何词串构成,不过概率  有大有小。例如:

  •  = 我刚吃过晚饭

  •  = 刚我过晚饭吃

显然,对于中文而言  是一个通顺而有意义的句子,而 则不是,所以对于中文来说, 。但不同语言来说,这两个概率值的大小可能会反转。

其次,另外一个例子是,如果我们给出了某个句子的一个节选,我们其实可以能够猜测后续的词应该是什么,例如

  • the large green __ . Possible answer may be “mountain” or “tree” ?

  • Kate swallowed the large green __ . Possible answer may be “pill” or “broccoli” ?

显然,如果我们知道这个句子片段更多前面的内容的情况下,我们会得到一个更加准确的答案。这就告诉我们,前面的(历史)信息越多,对后面未知信息的约束就越强。

如果我们有一个由  个词组成的序列(或者说一个句子),我们希望算得概率  ,根据链式规则,可得 


这个概率显然并不好算,不妨利用马尔科夫链的假设,即当前这个词仅仅跟前面几个有限的词相关,因此也就不必追溯到最开始的那个词,这样便可以大幅缩减上诉算式的长度。即 

特别地,对于  取得较小值的情况 
当 , 一个一元模型(unigram model)即为 

当 , 一个二元模型(bigram model)即为 

当 , 一个三元模型(trigram model)即为 

接下来的思路就比较明确了,可以利用最大似然法来求出一组参数,使得训练样本的概率取得最大值。


  • 对于unigram model而言,其中 表示 n-gram  在训练语料中出现的次数, 是语料库中的总字数(例如对于 yes no no no yes 而言,) 

  • 对于bigram model而言, 

  • 对于-gram model而言, 

来看一个具体的例子,假设我们现在有一个语料库如下,其中 是句首标记, 是句尾标记: 


下面我们的任务是来评估如下这个句子的概率: 

我们来演示利用trigram模型来计算概率的结果 

所以我们要求的概率就等于: 


再举一个来自文献[1]的例子,假设现在有一个语料库,我们统计了下面一些词出现的数量

NLPN-Gram自然语言处理模糊匹配编辑距离

 


下面这个概率作为其他一些已知条件给出: 

下面这个表给出的是基于Bigram模型进行计数之结果 

NLPN-Gram自然语言处理模糊匹配编辑距离

 


例如,其中第一行,第二列 表示给定前一个词是 “i” 时,当前词为“want”的情况一共出现了827次。据此,我们便可以算得相应的频率分布表如下。 

 


因为我们从表1中知道 “i” 一共出现了2533次,而其后出现 “want” 的情况一共有827次,所以 
现在设 ,则可以算得 



使用N-Gram模型时的数据平滑算法

有研究人员用150万词的训练语料来训练 trigram 模型,然后用同样来源的测试语料来做验证,结果发现23%的 trigram 没有在训练语料中出现过。这其实就意味着上一节我们所计算的那些概率有空为 0,这就导致了数据稀疏的可能性,我们的表3中也确实有些为0的情况。对语言而言,由于数据稀疏的存在,极大似然法不是一种很好的参数估计办法。

这时的解决办法,我们称之为“平滑技术”(Smoothing)或者 “减值” (Discounting)。其主要策略是把在训练样本中出现过的事件的概率适当减小,然后把减小得到的概率密度分配给训练语料中没有出现过的事件。实际中平滑算法有很多种,例如: 
  ▸ Laplacian (add-one) smoothing 
  ▸ Add-k smoothing 
  ▸ Jelinek-Mercer interpolation 
  ▸ Katz backoff 
  ▸ Absolute discounting 
  ▸ Kneser-Ney

对于这些算法的详细介绍,我们将在后续的文章中结合一些实例再来进行讨论。


A Final Word

如果你能从前面那些繁冗、复杂的概念和公式中挺过来,恭喜你,你对N-Gram模型已经有所认识了。尽管,我们还没来得及探讨平滑算法(但它即将出现在我的下一篇博文里,如果你觉得还未过瘾的话),但是其实你已经掌握了一个相对powerful的工具。你可以能会问,在实践中N-Gram模型有哪些具体应用,作为本文的结束,主页君便在此补充几个你曾见过的或者曾经好奇它是如何实现的例子。

Eg.1 
搜索引擎(Google或者Baidu)、或者输入法的猜想或者提示。你在用百度时,输入一个或几个词,搜索框通常会以下拉菜单的形式给出几个像下图一样的备选,这些备选其实是在猜想你想要搜索的那个词串。再者,当你用输入法输入一个汉字的时候,输入法通常可以联系出一个完整的词,例如我输入一个“刘”字,通常输入法会提示我是否要输入的是“刘备”。通过上面的介绍,你应该能够很敏锐的发觉,这其实是以N-Gram模型为基础来实现的,如果你能有这种觉悟或者想法,那我不得不恭喜你,都学会抢答了!


 


Eg.2 
某某作家或者语料库风格的文本自动生成。这是一个相当有趣的话题。来看下面这段话(该例子取材自文献【1】):

“You are uniformly charming!” cried he, with a smile of associating and now and then I bowed and they perceived a chaise and four to wish for.

你应该还没有感觉到它有什么异样吧。但事实上这并不是由人类写出的句子,而是计算机根据Jane Austen的语料库利用trigram模型自动生成的文段。(Jane Austen是英国著名女作家,代表作有《傲慢与偏见》等)

再来看两个例子,你是否能看出它们是按照哪位文豪(或者语料库)的风格生成的吗?

  • This shall forbid it should be branded, if renown made it empty.

  • They also point to ninety nine point six billion dollars from two hundred four oh three percent of the rates of interest stores as Mexico and Brazil on market conditions.

答案是第一个是莎士比亚,第二个是华尔街日报。最后一个问题留给读者思考,你觉得上面两个文段所运用的n-gram模型中,n应该等于多少?


以上是关于求一个基于java的模糊匹配算法的主要内容,如果未能解决你的问题,请参考以下文章

java中是如何实现基于文字标题的模糊匹配的,下面的代码是啥意思啊...

模糊搜索算法(近似字符串匹配算法)

字符串模糊匹配

“模糊匹配”字符串的算法

nodejs 匹配字符串问题

模糊匹配/分块算法