使用 Levenshtein 尋找彼此類似的字符串對

咱們爬來了一些數據,接下來以豆瓣暢銷書爲例。html

爬蟲爬來的數據有前端

['艾倫•圖靈傳','深刻理解計算機系統(原書第2版)','C++ Primer 中文版(第 5 版)','深刻理解計算機系統','Web性能權威指南']

而咱們系統中原有的數據有python

['艾倫·圖靈傳','深刻理解計算機系統(原書第2版)','C++ Primer 中文版(第 4 版)','深刻理解計算機系統']

作前端的同志可能一眼就看出來了,兩個數組中有三個元素是由於全半角的緣故,是不能全詞匹配的,而前兩本書事實上是同一本書。而《深刻理解計算機系統》是能夠全詞匹配到的,《Web性能權威指南》一書是能夠直接添加到數據庫的。git


解決方案一:算法

這個任務大能夠交給編輯去作,可是時間複雜度爲 N^2,連程序都吃不消跑,更別提讓編輯作了。數據庫

解決方案二:數組

去除全部的標點符號,或者將全部全角符號轉化爲半角。 去掉全部空格。 而後進行全詞匹配,這樣作有些魯莽,可是速度一點也不慢。機器學習

解決方案三:函數

我想到了用 jieba 進行中文分詞,性能

import jieba
book = '艾倫·圖靈傳'
word = jieba.cut(book)
words = list(word)
# words = ['艾倫', '·', '圖靈', '傳']

對於每本書咱們均可以進行這樣一個分詞操做,並能夠考慮將標點符號去除。 而後看每兩個數組的元素的重合狀況,但要考慮歸一化,緣由以下:

  1. ‘Python 開發指南’ 和 ‘皇家 Python 開發指南’ 元素的重合數爲 3 個單詞。
  2. ‘Python 開發指南’ 和 ‘皇家 Python 超級無敵開發指南’ 元素的重合數爲 3 個單詞。

顯然第 1 組看起來會比第 2 組類似一些,可是重合數是同樣的。使用重合數顯然不科學,咱們能夠經過除以分詞後單詞總數來計算它們的重合率。

解決方案四:

在想解決方案三的時候,我回憶起了之前作聚類算法的時候用的各類距離算法,對於實數的歐幾里得距離,用在 k-modes 中的對於類型數據的相異度量算法,前者在這裏顯然不適用,後者又顯得有點小題大作。而後我發現了一種叫作編輯距離 Edit Distance 的算法,又稱 Levenshtein 距離。[1]

編輯距離是指兩個字串之間,由一個轉成另外一個所需的最少編輯操做次數。 維基上給出了一個例子: 'kitten' 和 'sitting' 的編輯距離爲3:

  1. kitten → sitten (s 替換成 k)
  2. sitten → sittin (i 替換成 e)
  3. sittin → sitting (加上一個 g)

碰巧的是有一個開源的 python 包恰好能夠計算 Levenshtein 距離,能夠經過如下安裝:

pip install python-Levenshtein

而後就能夠計算編輯距離了:

import Levenshtein
texta = '艾倫·圖靈傳'
textb = '艾倫•圖靈傳'
print Levenshtein.distance(texta,textb)
# 3

很天然地會有這個疑問,爲何這裏的編輯距離會是3,咱們來看看具體進行的編輯操做。

Levenshtein.editops('艾倫·圖靈傳', '艾倫•圖靈傳')
[('insert', 6, 6), ('replace', 6, 7), ('replace', 7, 8)]

看起來只有一個字符的區別,可是這裏作了三次編輯操做,爲何?咱們來打印一下這兩個字符串的具體表達:

In [4]: a='艾倫·圖靈傳'
In [5]: a
Out[5]: '\xe8\x89\xbe\xe4\xbc\xa6\xc2\xb7\xe5\x9b\xbe\xe7\x81\xb5\xe4\xbc\xa0'
In [6]: a='艾'
In [7]: a
Out[7]: '\xe8\x89\xbe'

看到這裏就明白了,咱們犯了一個錯誤,把這兩個字符串存成了 string 類型,而在 string 類型中,默認的 utf-8 編碼下,一箇中文字符是用三個字節來表示的。因而這裏又牽扯到了 Python 中的 string 和 unicode 的區別。然而在這個字節串中,咱們的編輯距離,的的確確是3。

如今從新來計算距離。

print Levenshtein.distance(u'艾倫·圖靈傳',u'艾倫•圖靈傳')
# 1

如今就正確了,如今的編輯距離1就表明了兩本書的名字的差異度量。這時候就要開始作歸一化了,而巧的是,咱們發現這個 Levenshtein 包中自帶了一個類似度函數 jaro(),它能夠接受兩個字符串並給出從0到1範圍內的類似度。下面所顯示的0.888888888889便表示了這兩個字符串的類似度。

print Levenshtein.jaro(u'艾倫·圖靈傳',u'艾倫•圖靈傳')
# 0.888888888889

而後咱們能夠寫個嵌套的 for 循環,設置一個閾值 threshold,計算每一對書本的類似度,當他們超過某一閾值時,進行處理。這裏要注意的就是,不要重複檢驗書本對,即檢測(書本 A,書本 B)和(書本 B,書本 A),這樣能夠避免 N^2 的時間複雜度。


固然最後其實仍是須要人工介入的,好比遇到第三本書這種狀況,版次不同並不算同一本書。再固然,咱們是能夠對全部版次有關的字符進行特殊處理,好比出現版次字符,並且兩本書不相同的時候,把距離函數的輸出值加上一個修正參數。不過,這種狀況太多太多,仍是用人工處理好了,此時的兩本書爲同一本書的機率是很高的。再否則,只能上機器學習了,可是也是要你有訓練樣本的。

參考文獻: [1] wikipedia Levenshtein_distance [2] Levenshtein Document

相關文章
相關標籤/搜索