很多时候,我们在数据处理的时候需要进行模糊匹配,输入的内容和目标匹配内容并不能完全匹配,如果能有一部分匹配,那么根据需求我们也可以选择性的使用这些数据,而对于这些数据的具体匹配度,肯定会不同的数据有不同的结果。这样的数据分析处理场景,大多时候都是在进行字符串匹配时会遇到的情景,为了能够有一个具有说服力、标准化、通用性的匹配标准,我们通常会使用一些专业的算法去处理,今天给大家介绍一种办法,那就是 莱文斯坦距离(Levenshtein)
。
一、简介
莱文斯坦距离(Levenshtein)
是俄罗斯科学家弗拉基米尔·莱文斯坦(英语:Vladimir Levenshtein)在1965年提出这个概念。这是编辑距离①
的一种。指两个字串之间,由一个转成另一个所需的最少编辑操作次数。
注释① 编辑距离: 编辑距离是针对二个字符串(例如英文字)的差异程度的量化量测,量测方式是看至少需要多少次的处理才能将一个字符串变成另一个字符串。查看维基百科信息。
二、定义
莱文斯坦距离
的定义其实非常简单,有点递归的影子,先计算出最小长度字符串时的距离,再计算出下一个长度的字符串的距离,从而实现整个字符串的全部距离。它的函数定义如下:
如果分别用 |a| 和 |b| 表示 a, b 两个字符串的长度,那么它们的莱文斯坦距离为 \text{lev}_{a,b}(|a|,|b|),它符合:
\displaystyle
\text{lev}_{a,b}(i,j)=
\begin{cases}
\text{max}(i,\ \ j)\qquad\qquad\qquad\qquad\qquad\text{if}\ \ \ \text{min}(i,j)=0,\\\\
\text{min}\begin{cases}
\text{lev}_{a,b}(i-1,j)+1\\
\text{lev}_{a,b}(i,j-1)+1\\
\text{lev}_{a,b}(i-1,j-1)+1_{(a_i\neq b_j)}
\end{cases}\text{otherwise.}
\end{cases}
其中 1_{(a_i\neq b_j)} 是一个 指示性函数
,当 a_i=b_j时为 0 ,其它情况下为 1 。
\text{lev}_{a,b}(i,j) 表示 a 的前 i 个字符与 b 的前 j个字符之间的莱文斯坦距离。( i 和 j 都是从1开始的下标。)
我相信你看到这么大的一堆函数表达式,瞬间好像回到了高中时期,脑袋突然宕机,完全不知道说的是什么。不用担心看了下面的原理你就明白了, 其实这个函数表达式就是对原理的一个表示。
三、原理
我们先用人话翻译一下上面的函数式。
- 第一行:当给定两个字符串中,最短的字符串长度为0的话,那么这两个的莱文斯坦距离为最大的字符串的长度。
- 第二行:当给定的两个字符串中最短的字符串长度不为0,那么使用第二部分的规则。这部分规则为取三个值中最小的一个值作为结果,而这三个值又分别是计算a的上一个位置和b当前位置的结果加1;计算a的当前位置和b的上一个位置的结果加1;计算a的上一个位置和b的上一个位置的结果然后判断当前位置两个字符是否相等,不等则加1。
光是口说无凭,也依然很难理解,如果我们将这个原理通过表格的形式,会更容易理解一点,下面我们就以这两句话:
- "First"
- "Last"
作为例子,通过表格的形式来解释 莱文斯坦距离
的原理。首先我们将这两句话横竖放置在表的顶部和左部,就像这样:
F | i | r | s | t | ||
0 | 1 | 2 | 3 | 4 | 5 | |
L | 1 | |||||
a | 2 | |||||
s | 3 | |||||
t | 4 |
可以看到上表已经填写了一些数字,这些数字其实很好理解,最左上角的0
,可以认为是两个字符串缩减到了空字符串的时候,即两个空字符串的莱文斯坦距离肯定是0
,因为不需要任何编辑步数。而“F”
下面的值1
,则可以使用定义中的第一条,两个字符串中如果有一个空字符串,那么它们的莱文斯坦距离就是最长的字符串的长度
。很明显,字符串“F”与空字符串的莱文斯坦距离是1。同样的道理即可得到后面的2、3、4、5
。
现在开始进一步计算,我们把目光看向字符串“L”和“F”在表中相交的单元格,这时由于两个字符串都是有内容的,我们可以根据定义中的第二部分定义进行计算,根据定义,我们需要得到3个算式的值,然后取最小的那个值。我们一个一个算:
\text{lev}_{a,b}(i-1,j)+1 其中的(i-1)可以理解为上一个单元格,坐标减一嘛。也就是上表中“F”下面的这个格子,里面计算出值是1,因此这里的算式结果就是:1+1 即得:2。
\text{lev}_{a,b}(i,j-1)+1 同样的,只不过是另一个字符串的上一个单元格了。也就是上表中“L”右边的格子,我们计算出来的值是1,所以这里的算式结果也是:1+1 即得:2。
\text{lev}_{a,b}(i-1,j-1)+1_{(a_i\neq b_j)} 对于这个算式,我们就需要先知道对角线上一个格子内的值,我们已经计算出来了,等于0。然后再根据我们当前位置的两个字符串是否相同来确定是否+1,很显然“L”不等于“F”,因此这个算式最终结果为:0+1=1。
最后,我们将三个结果2
,2
,1
比较取最小的一个值,也就是1
是我们这个格子对应的两个字符串的莱文斯坦距离
。的却,字符串“L”要变成“F”只需要进行一次替换操作就可以了。
那么现在,我们这个表就变成了下面的样子:
F | i | r | s | t | ||
0 | 1 | 2 | 3 | 4 | 5 | |
L | 1 | 1 | ||||
a | 2 | |||||
s | 3 | |||||
t | 4 |
现在,我们已经大致的了解了这个计算流程,很容易我们就能够推导出接下来所有的空格。下面我已经把所有空格都填上对应的值了:
F | i | r | s | t | ||
0 | 1 | 2 | 3 | 4 | 5 | |
L | 1 | 1 | 2 | 3 | 4 | 5 |
a | 2 | 2 | 2 | 3 | 4 | 5 |
s | 3 | 3 | 3 | 3 | 3 | 4 |
t | 4 | 4 | 4 | 4 | 4 | 3 |
最后这两个字符串“First”
和Last
的莱文斯坦距离就是整个表最右下角的格子里面的数字3
。现在,你应该掌握了莱文斯坦距离
的精髓了。
四、立即体验
输入两个字符串,点击计算按钮,立刻体验。
字符串1:
字符串2:
五、实现
当我们了解了实现原理之后,用代码实现就只是时间问题了。不过你要庆幸,我们已经是站在许多巨人的肩膀上了,实现了这个原理的第三方库有许许多多。下面就列举几个:
npm库: