對於包含自然語言文件輸入的應用程式,使用者期望它具備拼寫檢查功能。因為從頭開始建構一個拼寫檢查器不是一項簡單的任務,所以這篇文章為您提供一個使用 Jazzy 的工作區。Jazzy 是一個開放原始程式碼的 Java 拼寫檢查器 API。Java 開發人員 Tom White 對基於電腦拼寫檢查背後的主要演算法進行了深入探討,然後向您展示 Jazzy API 如何才能幫助您把它們整合到 Java 應用程式中。電腦擅長執行快速搜索操作,可以根據指定的搜索詞,對大量儲存的資訊快速進行搜索。但是,拼寫檢查應用程式所要求的搜索能力,不僅僅是正確的字串匹配。在這篇文章中,我將介紹搜索演算法的一些歷史,包括語音匹配演算法(比如 Soundex 和 Metaphone ),字串相似性類型(例如動態程式設計演算法)。我會解釋這些演算法對於拼寫檢查來說各自的優勢與不足,然後介紹最後一個變種-- Aspell 演算法,這個演算法是專門為拼寫檢查應用程式撰寫的。
Aspell 演算法結合了前面搜索與匹配演算法的最佳特性,是 Jazzy 的底層框架,是 Java 平台的拼寫檢查器 API。在這篇文章的後半部分裡,您將看到 Jazzy 在 Java 框架裡是如何應用 Aspell 演算法的。我會向您展示 Jazzy 識別拼寫錯誤的單字並提供合適的修正 。在文章結尾,我會用一個實例,示範在 Jazzy 的幫助下,您可以很容易地把它的拼寫檢查特性合併到 Java 應用程式中。
正確拼出姓氏可能是個挑戰。如果一個人的姓不太常見,那麼當他們透過電話訂購的時候,經常發現名字被弄錯。即使是常見的名字也可能因為拼寫上的微小差異而拼錯,例如 Smith和 Smyth是發音相同的常見名字的兩個變體。
特別是在名字拼寫上,變化更豐富,這樣就形成了一些有趣的拼寫檢查演算法。我要介紹的第一個演算法類型是語音匹配演算法,它的目標是解決“哪個名字和聽起來像 x的名字匹配”這樣的問題。在搜索資料庫和其他參考應用程式裡,這種演算法類型相當普遍。例如,在搜索家族歷史時,使用者應該既能檢索到正確匹配,也會得到相似的匹配,這樣就有可能找到家族姓氏淵源流傳中發生變化或者在某些記錄中被拼寫錯誤的歷史。
Soundex 演算法從 1920 年起就一直被用來為所有美國人口做索引,是家族軟體常用的演算法。原始 Soundex 演算法在 1918 年由 Margaret K. Odell 和 Robert C. Russell 申請專利(請參閱 參考資料),他們當初是想“提供一個索引,按照發音而不是按照名字的字母表輸入名字,對名字分組”。
從實際上說,Soundex 演算法的運作方式是把某個字母表中的每個字母對應成代表它的語音組的一個數位程式。在這個方案裡,像 d和 t)這樣的字母在同一個組裡,因為它們發音相近(實際上每個字母都是用類似的機制發出聲音的),而母音則一概忽略。透過對整體單字應用這種對應,就產生了單字的語音“鍵”。發音相近的單字通常會有相同的鍵。例如, Smith和 Smyth 的 Soundex 都是
S530
。Soundex 最常見的一個變體透過 Donald E. Knuth 的 The Art of Computer Programming一書流行開來。您可以在清單 1 中看到這個演算法的 Java 實作。請注意該演算法使用了 Java 正規運算式,正規運算式只有在 Java 1.4 發行版之後才可用。
上面的程式相當簡潔,所以我逐行來說明它的功能:
- 行 01 到 05 對輸入進行規範化,把輸入變成大寫字母,去掉其他字元。
- 行 06 保證單字的第一個字母不變。
- 行 07 去掉後續的
H
或W
字母。
- 行 08 到 11 用字母的語音程式替換單字裡的每個字母。
- 行 12 刪除相鄰的相同語音程式。(請注意:這意味著,與母音的處理方式不同,插在中間的字元
H
和W
不會對組合相同程式的字母形成障礙。)
- 與行 06 類似,行 13 保證單字的第一個字母不變。
- 行 14 消除所有母音。
- 行 15 透過把單字裁剪成 4 個字母,形成 Soundex (可能要用字元
0
來填充)。
為了真正理解演算法,手動地逐行執行演算法會很有幫助。程式右手邊的列用於追蹤
word
變數的值,從輸入的名字 Ashcroft開始。對於演算法來說,這是個很好的測試使用案例,因為 s和 c組合,沒有理會插在中間的 h。(在相同類型的 Web 網站上可以找到的許多 Soundex 實作都沒有正確地實作這一規則。)不幸的是,Soundex 演算法是一個差勁的拼寫檢查備選方案。首先來說,發音不同的單字可能有相同的 soundex。例如, White和 Wood 的 soundex 碼相同,同為
W300
。這並不奇怪,因為 Soundex 演算法的設計,就是為了把發音 相似的名字組合在一起,而不是嚴格地按照發音 相同組合。雖然這個特性對於某些應用程式來說可能是理想的 -- 例如用來幫助電話操作員識別用不同重音說出的名字的應用程式 -- 但是它對拼寫檢查應用程式沒有用,因為它會產生太多的匹配。例如,拼錯的 algorithum一詞會與我的範例字典中的下列單字匹配:alacritous, alacrity, alcheringa, alcoran, algeria, algerian, algerians, algiers, algor, algorism, algorithm, algorithmic, algorithmically, algorithms, alizarin, alizarine, alkoran, alleger, allegers, allegoric, allegorical, allegorically, allegories, allegorist, allegorists, allegorizes, allegory, allegretto, allegrettos, allegro, allegros, allocheiria, allochiria, allocortex, allograft, allograph, allographic, allographs即使考慮到同一單字的變體( allegoric、 allegorical、 allegorically)造成的額外匹配,您通常也應該要求拼寫檢查演算法提供更加嚴格的匹配。 您應該還記得, Soundex 演算法也會把每個 soundex 程式裁剪成 4 個字元,這樣就疏忽了長單字的尾部,因此也就進一步增加了匹配的數量。而且麻煩還不止於此。
正如發音不同的單字有可能有相同的 soundex,反過來的情況也有可能發生:發音相同的單字,叫做 同音詞(homophone),可能有不同的程式。這是由於某些字母可能不發音,例如在 Thompson (
T512
)中的 p 造成它發音與 Thomson(T525
)相同,但程式不同,還有 Leigh (L200
)中的 gh與 Lee (L000
) ,也有同樣的問題。與此類似,單字的開始字母可能不同,但是不影響它的發音,例如 Carr(C600
)中的 c與 Karr(K600
)中的 k。Soundex 演算法本身造成了這個問題,因為它無法把每個單字中的原始字母對應成語音數位。所謂同音的問題,實際上產生於這樣一個現實:英語語言有不規範拼寫(可能比其他語言更甚)。雖然 Soundex 演算法有許多小的變體,但是他們都缺少對英語拼寫規則的認識,更不用說這些規則的例外了。這種不規範的後果就是, Soundex 不太適合做英語中的拼寫檢查。例如, Soundex 對於拼寫錯誤的 lam (
L500
),提供了一個正確拼寫形式 lamb(L510
)不同的語音撰寫程式。這樣,基於 Soundex 的拼寫檢查應用程式就無法把 lamb作為拼寫錯誤的 lam的修改建議。正是這個問題,引領著 Lawrence Phillips 找到了 Soundex 演算法的替代品,叫做 Metaphone。
Metaphone 語音匹配演算法Soundex 演算法參考資料),他們當初是想“提供一個索引,按照發音而不是按照名字的字母表輸入名字,對名字分組”。 清單1. Knuth 的 Soundex
|
程式說明 Soundex 用於拼寫檢查 同音問題
對於包含自然語言文件輸入的應用程式,使用者期望它具備拼寫檢查功能。因為從頭開始建構一個拼寫檢查器不是一項簡單的任務,所以這篇文章為您提供一個使用 Jazzy 的工作區。Jazzy 是一個開放原始程式碼的 Java 拼寫檢查器 API。Java 開發人員 Tom White 對基於電腦拼寫檢查背後的主要演算法進行了深入探討,然後向您展示 Jazzy API 如何才能幫助您把它們整合到 Java 應用程式中。
電腦擅長執行快速搜索操作,可以根據指定的搜索詞,對大量儲存的資訊快速進行搜索。但是,拼寫檢查應用程式所要求的搜索能力,不僅僅是正確的字串匹配。在這篇文章中,我將介紹搜索演算法的一些歷史,包括語音匹配演算法(比如 Soundex 和 Metaphone ),字串相似性類型(例如動態程式設計演算法)。我會解釋這些演算法對於拼寫檢查來說各自的優勢與不足,然後介紹最後一個變種-- Aspell 演算法,這個演算法是專門為拼寫檢查應用程式撰寫的。
Aspell 演算法結合了前面搜索與匹配演算法的最佳特性,是 Jazzy 的底層框架,是 Java 平台的拼寫檢查器 API。在這篇文章的後半部分裡,您將看到 Jazzy 在 Java 框架裡是如何應用 Aspell 演算法的。我會向您展示 Jazzy 識別拼寫錯誤的單字並提供合適的修正 。在文章結尾,我會用一個實例,示範在 Jazzy 的幫助下,您可以很容易地把它的拼寫檢查特性合併到 Java 應用程式中。
正確拼出姓氏可能是個挑戰。如果一個人的姓不太常見,那麼當他們透過電話訂購的時候,經常發現名字被弄錯。即使是常見的名字也可能因為拼寫上的微小差異而拼錯,例如 Smith和 Smyth是發音相同的常見名字的兩個變體。
特別是在名字拼寫上,變化更豐富,這樣就形成了一些有趣的拼寫檢查演算法。我要介紹的第一個演算法類型是語音匹配演算法,它的目標是解決“哪個名字和聽起來像 x的名字匹配”這樣的問題。在搜索資料庫和其他參考應用程式裡,這種演算法類型相當普遍。例如,在搜索家族歷史時,使用者應該既能檢索到正確匹配,也會得到相似的匹配,這樣就有可能找到家族姓氏淵源流傳中發生變化或者在某些記錄中被拼寫錯誤的歷史。
|
Soundex 演算法從 1920 年起就一直被用來為所有美國人口做索引,是家族軟體常用的演算法。原始 Soundex 演算法在 1918 年由 Margaret K. Odell 和 Robert C. Russell 申請專利(請參閱
從實際上說,Soundex 演算法的運作方式是把某個字母表中的每個字母對應成代表它的語音組的一個數位程式。在這個方案裡,像 d和 t)這樣的字母在同一個組裡,因為它們發音相近(實際上每個字母都是用類似的機制發出聲音的),而母音則一概忽略。透過對整體單字應用這種對應,就產生了單字的語音“鍵”。發音相近的單字通常會有相同的鍵。例如, Smith和 Smyth 的 Soundex 都是 S530
。
Soundex 最常見的一個變體透過 Donald E. Knuth 的 The Art of Computer Programming一書流行開來。您可以在清單 1 中看到這個演算法的 Java 實作。請注意該演算法使用了 Java 正規運算式,正規運算式只有在 Java 1.4 發行版之後才可用。
上面的程式相當簡潔,所以我逐行來說明它的功能:
- 行 01 到 05 對輸入進行規範化,把輸入變成大寫字母,去掉其他字元。
- 行 06 保證單字的第一個字母不變。
-
行 07 去掉後續的
H
或W
字母。
- 行 08 到 11 用字母的語音程式替換單字裡的每個字母。
-
行 12 刪除相鄰的相同語音程式。(請注意:這意味著,與母音的處理方式不同,插在中間的字元
H
和W
不會對組合相同程式的字母形成障礙。)
- 與行 06 類似,行 13 保證單字的第一個字母不變。
- 行 14 消除所有母音。
-
行 15 透過把單字裁剪成 4 個字母,形成 Soundex (可能要用字元
0
來填充)。
為了真正理解演算法,手動地逐行執行演算法會很有幫助。程式右手邊的列用於追蹤 word
變數的值,從輸入的名字 Ashcroft開始。對於演算法來說,這是個很好的測試使用案例,因為 s和 c組合,沒有理會插在中間的 h。(在相同類型的 Web 網站上可以找到的許多 Soundex 實作都沒有正確地實作這一規則。)
不幸的是,Soundex 演算法是一個差勁的拼寫檢查備選方案。首先來說,發音不同的單字可能有相同的 soundex。例如, White和 Wood 的 soundex 碼相同,同為 W300
。這並不奇怪,因為 Soundex 演算法的設計,就是為了把發音 相似的名字組合在一起,而不是嚴格地按照發音 相同組合。雖然這個特性對於某些應用程式來說可能是理想的 -- 例如用來幫助電話操作員識別用不同重音說出的名字的應用程式 -- 但是它對拼寫檢查應用程式沒有用,因為它會產生太多的匹配。例如,拼錯的 algorithum一詞會與我的範例字典中的下列單字匹配:
alacritous, alacrity, alcheringa, alcoran, algeria, algerian, algerians, algiers, algor, algorism, algorithm, algorithmic, algorithmically, algorithms, alizarin, alizarine, alkoran, alleger, allegers, allegoric, allegorical, allegorically, allegories, allegorist, allegorists, allegorizes, allegory, allegretto, allegrettos, allegro, allegros, allocheiria, allochiria, allocortex, allograft, allograph, allographic, allographs
即使考慮到同一單字的變體( allegoric、 allegorical、 allegorically)造成的額外匹配,您通常也應該要求拼寫檢查演算法提供更加嚴格的匹配。 您應該還記得, Soundex 演算法也會把每個 soundex 程式裁剪成 4 個字元,這樣就疏忽了長單字的尾部,因此也就進一步增加了匹配的數量。而且麻煩還不止於此。
正如發音不同的單字有可能有相同的 soundex,反過來的情況也有可能發生:發音相同的單字,叫做 同音詞(homophone),可能有不同的程式。這是由於某些字母可能不發音,例如在 Thompson ( T512
)中的 p 造成它發音與 Thomson( T525
)相同,但程式不同,還有 Leigh ( L200
)中的 gh與 Lee ( L000
) ,也有同樣的問題。與此類似,單字的開始字母可能不同,但是不影響它的發音,例如 Carr( C600
)中的 c與 Karr( K600
)中的 k。Soundex 演算法本身造成了這個問題,因為它無法把每個單字中的原始字母對應成語音數位。
所謂同音的問題,實際上產生於這樣一個現實:英語語言有不規範拼寫(可能比其他語言更甚)。雖然 Soundex 演算法有許多小的變體,但是他們都缺少對英語拼寫規則的認識,更不用說這些規則的例外了。這種不規範的後果就是, Soundex 不太適合做英語中的拼寫檢查。例如, Soundex 對於拼寫錯誤的 lam ( L500
),提供了一個正確拼寫形式 lamb( L510
)不同的語音撰寫程式。這樣,基於 Soundex 的拼寫檢查應用程式就無法把 lamb作為拼寫錯誤的 lam的修改建議。正是這個問題,引領著 Lawrence Phillips 找到了 Soundex 演算法的替代品,叫做 Metaphone。 演算法
Metaphone 演算法背後的想法,首先發表在 1990 年的 Computer Language雜誌上(請參閱 參考資料),這個演算法明確地對英語發音的公共規則進行了撰寫程式,而這正是 Soundex 沒有解決的問題。例如, Metaphone 演算法包含一個明確的規則:在字母 b在單字末尾出現在字母 m後面時,就刪除它。這個規則保證了 lam和 lamb 會有相同的撰寫程式( LM
),這樣就使拼寫檢查應用程式能夠為 lam提供正確的替換。
Metaphone 演算法使用了 16 個輔音類別,由下列字元代表:
|
字元 0
是零,用來代表 th 的聲音。就像在 Soundex 演算法裡一樣,第一個字母被保留,最後的程式被裁剪成四個字元,但是如果短於四個字元,也並不填充。迭代的字母和母音通常被刪除,與母音的處理一樣。Metaphone 演算法整體上是一套規則集,可以把字母組合對應成輔音類別。這個演算法的 Java 實作需要幾百行程式,具體可以參閱 Apache Jakarta Commons Codec 專案中的 Metaphone 程式(請參閱 參考資料)。在清單 2 中,您可以看到當您把 Apache 的 Metaphone
類別用作 JUnit 的測試使用案例,檢查單字 lamb的程式時發生的情況:
|
雖然在規則裡仍然有一些缺陷,但 Metaphone 演算法在 Soundex 上有了提高。例如,Metaphone 的作者 Phillips 指出, Bryan( BRYN
)和 Brian) BRN
)應該有相同的程式。 Phillips 在 2000 年 6 月出版的 C/C++ Users Journal 上發表了他對 Metaphone 的模糊匹配(是這麼叫的)改進的嘗試。 DoubleMetaphone 演算法對原來的輔音類別做了一些修正,它把所有的開始母音都撰寫程式成 A
,所以不再使用 Soundex 演算法。更加根本的變化是,DoubleMetaphone 被撰寫成可以為多音詞回傳不同的程式。例如, hegemony中的 g 可以發輕聲,也可以發重音,所以演算法既回傳 HJMN
,也可以回傳 HKMN
。除了這些例子之外,Metaphone 演算法中的多數單字還是回傳單一鍵。您可以參見清單 3 中摘錄的 Apache 的 DoubleMetaphone
類別的程式。
清單3. 使用 Apache DoubleMetaphone 類別
|
雖然 Soundex 和 Metaphone 演算法都解決了語音模糊的匹配問題,但是如果不能糾正打字錯誤,那麼拼寫檢查應用程式是不完整的。當您的手指在鍵盤上滑過,打的是 labm ( LBM
)而不是 lamb( LM
), 打字錯誤就出現了。語音匹配演算法不能用它的替換來匹配這種拼寫錯誤,因為兩個單字聽起來是不同的。為了解決這類問題,您的拼寫檢查應用程式必須包括字串相似性演算法。
|
您還記得這樣的字謎嗎--每次只允許修改單字的一個字母,就能把它變換成另外一個單字?例如, ship可以透過逐步修改變成 crow,透過中間單字 shop、 chop和 crop。這種遊戲為您提供了一條路,可以清楚地理解兩個單字之間的距離這一概念。 距離是從一個單字變換成另外一個單字所需要的步數,要求是每次只能改變一個字母,而且每步都要使用字典中實際存在的單字。我把這叫做 字謎距離(puzzle distance)。在這個範例裡, ship和 crow之間的字謎距離是 4。
雖然我們經常把距離當作是空間中二點之間的物理度量,但是數學家則用更具一般性的概念把它定義為 度量(metric)。這個定義讓您可以在不同的應用程式中使用距離的概念;在這裡,您感興趣的是兩個字串或兩個單字之間的距離。它的意義在於,對於拼寫錯誤的單字,您應該尋找和它“接近”(這就使用了距離的定義)的單字。距離度量的任何定義都必須滿足一些可以度量的屬性;例如,距離永遠不可能為負。
雖然順序比較有許多方面(請參閱 參考資料),但是您的目的是找到距離的定義,使距離有助於實作良好的拼寫校正。前面定義的字謎距離至少有一個理由不適合做這項工作:拼寫錯誤的單字比起正確拼寫的單字來說,通常不止錯了一個字母。例如,對於拼錯的 puzzel,找不到“路碑”可以到達拼寫正確的英文單字。幸運的是,已經設計了大量適用於拼寫檢查的度量方式。
|
動態程式設計演算法從本質上看是一種窮舉方法,它會考慮到把原本的單字轉換成目標單字的所有不同方法,進而找到成本最小、或者單字間距離最短的方法。 Levenshtein 距離演算法是動態程式設計演算法的一個具體實作,它允許進行三類操作,把原本的單字 x轉換成目標單字 y:
- 把單字 x中的一個字元 替換成單字 y中的一個字元
- 把單字 x中的一個字元 刪除
- 在單字 y中 插入一個字元
每個操作都會有一定的成本,而總距離就是從單字 x變換到單字 y 的最小成本。從直觀上看,基於這些操作的演算法應該可以很好地進行拼寫校正,因為打字錯誤無外乎是這些操作所涉及的鍵入錯誤。(實際上, Levenshtein 距離也稱作 編輯距離。)例如,當我把單字 wrong打成 wromg(按了 m鍵,而不是 n 鍵)的時候,就是一個替換錯誤;當我打成 wromng(按了 m鍵,還有 n鍵)的時候,就是一個刪除錯誤;而當我打成 wrog(遺漏了 n 鍵),就是一個插入錯誤。
為了更容易理解動態程式設計演算法,可以畫一個表格,它的行對應原本的單字的字母,它的列對應目標單字的字母。處在 (i, j)位置的單格代表從原本的單字的 i字母到目標單字的 j字母的最小距離。
對於 Levenshtein 距離,刪除和插入的成本為 1。如果字元有差異,那麼替換的成本為 1,否則為 0。開始演算法的時候,您先填充第一行,第一行對應著空的原本的單字,這樣它就是插入 0,1,..., j個字母的成本。同樣,第一列對應著空的目標單字,所以它就是刪除 0, 1, ..., i個字母的成本。如果您以 pzzel到 puzzle 的轉換為例,那麼您會得到如 圖 1 所示的網格。
接下來,您要計算餘下的每個單格的值,透過考慮它的三個鄰居來計算:上、左、對角上和左。圖 2 顯示了這個計算方案。
對角 | 上 |
左 | Min( 對角+ 替換成本, 上+ 刪除成本, 左+ 插入成本 ) |
例子結果網格如圖 3 如示。右下角單格的成本是 3,是 pzzel和 puzzle之間的 Levenshtein 成本。
作為額外優點, Levenshtein 演算法還為您提供了一系列操作,也叫做 校準(alignment),它構成了轉換。一對單字通常有不止一次校準。校準對應著沿圖表的箭頭從左上角單格到右下角單格的最小成本路徑。例如, 清單 4表示的校準(在 圖 3中以紅色箭頭表示),可以按照下面的操作順序,一個字母一個字母地讀成:
- 把 p替換成 p(成本為 0)
- 插入 u(成本為 1)
- 把 z替換成 z(成本為 0)
- 把 z替換成 z(成本為 0)
- 插入 l(成本為 1)
- 把 e替換成 e(成本為 0)
- 刪除 l(成本為 1)
|
清單 5 列出了 Levenshtein 演算法的一個簡單而直觀的 Java 實作。 LevenshteinDistanceMetric
類別有些類似於 Apache Jakarta Commons 專案的 StringUtils
類別。這些實作的限制是:它們不能處理大型字串,因為它們的儲存需求為 O(mn), 其中 m和 n 分別是原本的單字和目標單字的長度。如果您只需要計算距離,不需要校準,就像通常情況那樣,那麼可以很容易地把空間需求降到 O(n),因為計算下一行只需要前面一行。針對 Apache 版本已經提出了一個修正建議(請參閱 參考資料),但是它在本文寫作的時候還沒有被併進來(2.0版)。
請注意: Levenshtein 演算法的執行時間總是 O(mn)。所以,如果在非常大的字典裡尋找拼寫錯誤的最相近匹配,這個演算法就太慢了。
|
|
迄今為止,我已經介紹了兩種拼寫檢查方法:語音匹配和順序比較。由於它們各自都沒有提供完整的解決方案,所以撰寫了一個把它們組合起來的演算法。下面是從 GNU Aspell 手冊中引用的內容:
[Aspell] 背後的秘密來自於整合了 Lawrence Philips 優秀的 metaphone 演算法和 Ispell 的靠近遺漏(near miss)策略,它會插入空格或連字元,交換兩個相鄰字母,改變一個字母,刪除一個字母,或者增加一個字母。
Jazzy 是 GPL/LGPL 協議下的基於 Java 的拼寫檢查器 API,它基於 Aspell 演算法,該演算法最初是用 C++ 撰寫的。
如果進行拼寫檢查的單字不在字典裡,那麼 Aspell 演算法就會假定它是拼寫錯誤的。在這種情況下,演算法用以下步驟來建立一個經過排序的修正建議列表:
- 加入拼寫錯誤靠近的語音匹配:加入字典中所有與拼寫錯誤單字語音撰寫程式相同的單字, 以及與拼寫錯誤單字的編輯距離小於指定閾值的所有單字。
- 加入與拼寫錯誤單字的“靠近遺漏”(near miss)接近的語音匹配:加入與拼寫錯誤單字只差一個編輯操作的所有單字的語音程式。對於這些程式,加入字典中所有與拼寫錯誤單字語音撰寫程式相同的單字, 以及 與拼寫錯誤單字的編輯距離小於指定閾值的單字。
- 最佳猜測:如果沒有找到建議,就加入字典中所有與拼寫錯誤的單字的語音程式相同的單字, 以及與拼寫錯誤的單字編輯距離最小的單字。
- 排序:按照編輯距離排序單字,把每一步驟中找到的單字放在一起。
Aspell 演算法的優勢在於它利用編輯距離的方式,它在單字級別上和語音程式級別上都使用編輯距離。在實務中,這可以形成足夠的模糊匹配,進而為拼寫錯誤單字形成良好的修正建議。
在 Jazzy 中使用的編輯距離與以前在 Levenshtein 距離中的定義不同。除了替代、刪除、插入之外,Jazzy 還包括了交換相鄰字母、改變字母大小寫的操作。操作的成本是可設定的。預設的語音撰寫程式方式是 Metaphone,但是也可以使用一個語音轉換規則檔(請參閱 參考資料),檔以表格的方式定義了像 Metaphone 這樣的轉換規則。表格驅動的方式使得可以很容易地把基於 Jazzy 的檢查器設定為支援其他語言。
|
從現在開始,我要把精力放在描述用 Jazzy API 實際建立一個拼寫檢查器上。清單 6 示範了如何用 Jazzy 撰寫一個 Java 拼寫檢查器。
|
main()
方法用命令列指定的檔建立了一個 SpellDictionary
。 SpellDictionaryHashMap
實作在記憶體中保存單字,這樣比較快,但是對於大型字典不適合。 (對於容易引起記憶體不足的應用程式,還提供了基於磁片的實作。) SpellDictionary
被用來構造 SpellChecker
物件,在用標準輸入填充之前,先用它註冊 SpellCheckListener
。拼寫檢查器通常內嵌在使用者驅動的應用程式裡,而事件驅動的設計本身就適合這類程式。在這個例子裡,偵聽器( SuggestionListener
)只是在接收到 SpellCheckEvent
事件時,向標準輸出寫出拼寫錯誤和建議列表。清單 7 顯示了一個執行範例。
|
這個例子非常簡單,更複雜的應用程式可以利用 Jazzy 對使用者字典管理的支援,執行向字典增加單字、忽略單字、用選中的修正自動替換迭代錯誤拼寫等任務。要獲得詳細資訊,請參閱 SpellCheckEvent
(在 參考資料中)的 API 文件。