介绍 Java 平台的 Jazzy:一种新的拼写检查器 API
計算機擅長執行快速搜索操作,可以根據給定的搜索詞,對大量存儲的信息快速進行搜索。但是,拼寫檢查應用程序所要求的搜索能力,不僅僅是正確的字符串匹配。在這篇文章中,我將介紹搜索算法的一些歷史,包括語音匹配算法(比如 Soundex 和 Metaphone ),字符串相似性類型(例如動態編程算法)。我會解釋這些算法對于拼寫檢查來說各自的優勢與不足,然后介紹最后一個變種-- Aspell 算法,這個算法是專門為拼寫檢查應用程序編寫的。
Aspell 算法結合了前面搜索與匹配算法的最佳特性,是 Jazzy 的底層框架,是 Java 平臺的拼寫檢查器 API。在這篇文章的后半部分里,您將看到 Jazzy 在 Java 框架里是如何應用 Aspell 算法的。我會向您展示 Jazzy 識別拼寫錯誤的單詞并提供合適的修正 。在文章結尾,我會用一個實例,演示在 Jazzy 的幫助下,您可以很容易地把它的拼寫檢查特性合并到 Java 應用程序中。
語音匹配算法
正確拼出姓氏可能是個挑戰。如果一個人的姓不太常見,那么當他們通過電話訂購的時候,經常發現名字被弄錯。即使是常見的名字也可能因為拼寫上的微小差異而拼錯,例如 Smith和 Smyth是發音相同的常見名字的兩個變體。
特別是在名字拼寫上,變化更豐富,這樣就形成了一些有趣的拼寫檢查算法。我要介紹的第一個算法類型是語音匹配算法,它的目標是解決“哪個名字和聽起來像 x的名字匹配”這樣的問題。在搜索數據庫和其他參考應用程序里,這種算法類型相當普遍。例如,在搜索家族歷史時,用戶應當既能檢索到正確匹配,也會得到相似的匹配,這樣就有可能找到家族姓氏淵源流傳中發生變化或者在某些記錄中被拼寫錯誤的歷史。
Soundex 算法
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 發行版之后才可用。
清單1. Knuth 的 Soundex
public class KnuthSoundex implements PhoneticEncoder {
? //??????????????????????????????????????????? ABCDEFGHIJKLMNOPQRSTUVWXYZ
? private static final String SOUNDEX_DIGITS = "01230120022455012623010202";
? public String calculateCode(String string) {
??? String word = string.toUpperCase();???????????????????????????????? // 01 ASHCROFT
??? word = word.replaceAll("[^A-Z]", "");?????????????????????????????? // 02
??? if (word.length() == 0) {?????????????????????????????????????????? // 03
????? return "";??????????????????????????????????????????????????????? // 04
??? }?????????????????????????????????????????????????????????????????? // 05
??? char first = word.charAt(0);??????????????????????????????????????? // 06
??? word = first + word.substring(1).replaceAll("[HW]", "");??????????? // 07 ASCROFT
??? StringBuffer sndx = new StringBuffer();???????????????????????????? // 08
??? for (int i = 0; i < word.length(); i++) {?????????????????????????? // 09
????? sndx.append(SOUNDEX_DIGITS.charAt((int) (word.charAt(i) - 'A'))); // 10
??? }?????????????????????????????????????????????????????????????????? // 11
??? word = sndx.toString().replaceAll("(.)//1+", "$1");???????????????? // 12 026013
??? word = first + word.substring(1);?????????????????????????????????? // 13 A26013
??? word = word.replaceAll("0", "");??????????????????????????????????? // 14 A2613
??? return (word + "000").substring(0, 4);????????????????????????????? // 15 A261
? }
}
????
代碼說明
上面的代碼相當簡潔,所以我逐行來說明它的功能:
行 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 算法是一個差勁的拼寫檢查備選方案。首先來說,發音不同的單詞可能有相同的 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 算法
Metaphone 算法背后的想法,首先發表在 1990 年的 Computer Language雜志上(請參閱 參考資料),這個算法明確地對英語發音的公共規則進行了編碼,而這正是 Soundex 沒有解決的問題。例如, Metaphone 算法包含一個明確的規則:在字母 b在單詞末尾出現在字母 m后面時,就刪除它。這個規則保證了 lam和 lamb 會有相同的編碼( LM ),這樣就使拼寫檢查應用程序能夠為 lam提供正確的替換。
Metaphone 算法使用了 16 個輔音類,由下列字符代表:
B X S K J T F H L M N P R 0 W Y
字符 0 是零,用來代表 th 的聲音。就像在 Soundex 算法里一樣,第一個字母被保留,最后的代碼被裁剪成四個字符,但是如果短于四個字符,也并不填充。重復的字母和元音通常被刪除,與元音的處理一樣。Metaphone 算法整體上是一套規則集,可以把字母組合映射成輔音類。這個算法的 Java 實現需要幾百行代碼,具體可以參閱 Apache Jakarta Commons Codec 項目中的 Metaphone 代碼(請參閱 參考資料)。在清單 2 中,您可以看到當您把 Apache 的 Metaphone 類用作 JUnit 的測試用例,檢查單詞 lamb的代碼時發生的情況:
清單2. 使用 Apache Metaphone 類
import junit.framework.TestCase;
import org.apache.commons.codec.language.Metaphone;
public class ApacheMetaphoneTest extends TestCase {
? public void test() {
??? Metaphone metaphone = new Metaphone();
????? assertEquals("LM", metaphone.encode("lam"));
????? assertEquals("LM", metaphone.metaphone("lam"));
????? assertEquals(metaphone.encode("lamb"), metaphone.encode("lam"));
????? assertTrue(metaphone.isMetaphoneEqual("lamb", "lam"));
? }
}
?
雖然在規則里仍然有一些缺陷,但 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 類
import junit.framework.TestCase;
import org.apache.commons.codec.language.DoubleMetaphone;
public class ApacheDoubleMetaphoneTest extends TestCase {
? public void test() {
??? DoubleMetaphone metaphone = new DoubleMetaphone();
??? assertEquals("HJMN", metaphone.encode("hegemony"));
??? assertEquals("HJMN", metaphone.doubleMetaphone("hegemony"));
??? assertEquals("HJMN", metaphone.doubleMetaphone("hegemony", false));
??? assertEquals("HKMN", metaphone.doubleMetaphone("hegemony", true));
? }
}
?
雖然 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 所示的網格。
圖1. Levenshtein 算法的第一階段
?
接下來,您要計算余下的每個單元格的值,通過考慮它的三個鄰居來計算:上、左、對角上和左。圖 2 顯示了這個計算方案。
圖2:如何計算單元格的成本
對角? 上?
左? Min(
對角+ 替換成本,
上+ 刪除成本,
左+ 插入成本
)?
例子結果網格如圖 3 如示。右下角單元格的成本是 3,是 pzzel和 puzzle之間的 Levenshtein 成本。
圖3. Levenshtein 算法的最后階段
?
Levenshtein 算法的屬性
作為額外優點, Levenshtein 算法還為您提供了一系列操作,也叫做 校準(alignment),它構成了轉換。一對單詞通常有不止一次校準。校準對應著沿圖表的箭頭從左上角單元格到右下角單元格的最小成本路徑。例如, 清單 4表示的校準(在 圖 3中以紅色箭頭表示),可以按照下面的操作順序,一個字母一個字母地讀成:
把 p替換成 p(成本為 0)
插入 u(成本為 1)
把 z替換成 z(成本為 0)
把 z替換成 z(成本為 0)
插入 l(成本為 1)
把 e替換成 e(成本為 0)
刪除 l(成本為 1)
?
清單4. pzzel 和 puzzle 之間的校準
p-zz-el
puzzle-
?
Levenshtein 算法的 Java 實現
清單 5 列出了 Levenshtein 算法的一個簡單而直觀的 Java 實現。 LevenshteinDistanceMetric 類有些類似于 Apache Jakarta Commons 項目的 StringUtils 類。這些實現的限制是:它們不能處理大型字符串,因為它們的存儲需求為 O(mn), 其中 m和 n 分別是源單詞和目標單詞的長度。如果您只需要計算距離,不需要校準,就像通常情況那樣,那么可以很容易地把空間需求降到 O(n),因為計算下一行只需要前面一行。針對 Apache 版本已經提出了一個修正建議(請參閱 參考資料),但是它在本文寫作的時候還沒有被合并進來(2.0版)。
請注意: Levenshtein 算法的運行時間總是 O(mn)。所以,如果在非常大的字典里查找拼寫錯誤的最相近匹配,這個算法就太慢了。
清單 5. Levenshtein 距離算法的實現
public class LevenshteinDistanceMetric implements SequenceMetric {
? /**
?? * Calculates the distance between Strings x and y using the
?? * <b>Dynamic Programming</b> algorithm.
?? */
? public final int distance(String x, String y) {
??? int m = x.length();
??? int n = y.length();
??? int[][] T = new int[m + 1][n + 1];
??? T[0][0] = 0;
??? for (int j = 0; j < n; j++) {
????? T[0][j + 1] = T[0][j] + ins(y, j);
??? }
??? for (int i = 0; i < m; i++) {
????? T[i + 1][0] = T[i][0] + del(x, i);
????? for (int j = 0; j < n; j++) {
??????? T[i + 1][j + 1] =? min(
??????????? T[i][j] + sub(x, i, y, j),
??????????? T[i][j + 1] + del(x, i),
??????????? T[i + 1][j] + ins(y, j)
??????? );
????? }
??? }
??? return T[m][n];
? }
? private int sub(String x, int xi, String y, int yi) {
??? return x.charAt(xi) == y.charAt(yi) ? 0 : 1;
? }
? private int ins(String x, int xi) {
??? return 1;
? }
? private int del(String x, int xi) {
??? return 1;
? }
? private int min(int a, int b, int c) {
??? return Math.min(Math.min(a, b), c);
? }
}
介紹 Jazzy
迄今為止,我已經介紹了兩種拼寫檢查方法:語音匹配和順序比較。由于它們各自都沒有提供完整的解決方案,所以編寫了一個把它們組合起來的算法。下面是從 GNU Aspell 手冊中引用的內容:
[Aspell] 背后的秘密來自于整合了 Lawrence Philips 優秀的 metaphone 算法和 Ispell 的靠近遺漏(near miss)策略,它會插入空格或連字符,交換兩個相鄰字母,改變一個字母,刪除一個字母,或者增加一個字母。
Jazzy 是 GPL/LGPL 協議下的基于 Java 的拼寫檢查器 API,它基于 Aspell 算法,該算法最初是用 C++ 編寫的。
Aspell 算法和 Jazzy
如果進行拼寫檢查的單詞不在字典里,那么 Aspell 算法就會假定它是拼寫錯誤的。在這種情況下,算法用以下步驟來建立一個經過排序的修正建議列表:
加入拼寫錯誤靠近的語音匹配:加入字典中所有與拼寫錯誤單詞語音編碼相同的單詞, 以及與拼寫錯誤單詞的編輯距離小于指定閾值的所有單詞。
加入與拼寫錯誤單詞的“靠近遺漏”(near miss)接近的語音匹配:加入與拼寫錯誤單詞只差一個編輯操作的所有單詞的語音代碼。對于這些代碼,加入字典中所有與拼寫錯誤單詞語音編碼相同的單詞, 以及 與拼寫錯誤單詞的編輯距離小于指定閾值的單詞。
最佳猜測:如果沒有找到建議,就加入字典中所有與拼寫錯誤的單詞的語音代碼相同的單詞, 以及與拼寫錯誤的單詞編輯距離最小的單詞。
排序:按照編輯距離排序單詞,把每一步驟中找到的單詞放在一起。
Aspell 算法的優勢在于它利用編輯距離的方式,它在單詞級別上和語音代碼級別上都使用編輯距離。在實踐中,這可以形成足夠的模糊匹配,從而為拼寫錯誤單詞形成良好的修正建議。
編輯距離說明
在 Jazzy 中使用的編輯距離與以前在 Levenshtein 距離中的定義不同。除了替代、刪除、插入之外,Jazzy 還包括了交換相鄰字母、改變字母大小寫的操作。操作的成本是可配置的。缺省的語音編碼方式是 Metaphone,但是也可以使用一個語音轉換規則文件(請參閱 參考資料),文件以表格的方式定義了像 Metaphone 這樣的轉換規則。表格驅動的方式使得可以很容易地把基于 Jazzy 的檢查器配置為支持其他語言。
建立拼寫檢查器
從現在開始,我要把精力放在描述用 Jazzy API 實際建立一個拼寫檢查器上。清單 6 演示了如何用 Jazzy 編寫一個 Java 拼寫檢查器。
清單6. 一個簡單的拼寫檢查器
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.List;
import com.swabunga.spell.engine.SpellDictionary;
import com.swabunga.spell.engine.SpellDictionaryHashMap;
import com.swabunga.spell.event.SpellCheckEvent;
import com.swabunga.spell.event.SpellCheckListener;
import com.swabunga.spell.event.SpellChecker;
import com.swabunga.spell.event.StringWordTokenizer;
public class Suggest {
? public static class SuggestionListener implements SpellCheckListener {
??? public void spellingError(SpellCheckEvent event) {
????? System.out.println("Misspelling: " + event.getInvalidWord());
????? List suggestions = event.getSuggestions();
????? if (suggestions.isEmpty()) {
??????? System.out.println("No suggestions found.");
????? } else {
??????? System.out.print("Suggestions: ");
??????? for (Iterator i = suggestions.iterator(); i.hasNext();) {
????????? System.out.print(i.next());
????????? if (i.hasNext()) {
??????????? System.out.print(", ");
????????? }
??????? }
??????? System.out.println();
????? }
??? }
? }
? public static void main(String[] args) throws Exception {
??? if (args.length < 1) {
????? System.err.println("Usage: Suggest <dictionary file>");
????? System.exit(1);
??? }
??? SpellDictionary dictionary = new SpellDictionaryHashMap(new File(args[0]));
??? SpellChecker spellChecker = new SpellChecker(dictionary);
??? spellChecker.addSpellCheckListener(new SuggestionListener());
??? BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
??? while (true) {
????? System.out.print("Enter line to spell check (return to exit): ");
????? String line = in.readLine();
????? if (line.length() == 0) {
??????? break;
????? }
????? spellChecker.checkSpelling(new StringWordTokenizer(line));
??? }
? }
}
?
main() 方法用命令行指定的文件建立了一個 SpellDictionary 。 SpellDictionaryHashMap 實現在內存中保存單詞,這樣比較快,但是對于大型字典不適合。 (對于容易引起內存不足的應用程序,還提供了基于磁盤的實現。) SpellDictionary 被用來構造 SpellChecker 對象,在用標準輸入填充之前,先用它注冊 SpellCheckListener 。拼寫檢查器通常內嵌在用戶驅動的應用程序里,而事件驅動的設計本身就適合這類程序。在這個例子里,偵聽器( SuggestionListener )只是在接收到 SpellCheckEvent 事件時,向標準輸出寫出拼寫錯誤和建議列表。清單 7 顯示了一個運行示例。
清單7. 用 Jazzy 進行拼寫檢查
Enter line to spell check (return to exit): choklut biskit
Misspelling: choklut
Suggestions: chocolate
Misspelling: biskit
Suggestions: biscuit
Enter line to spell check (return to exit):
?
這個例子非常簡單,更復雜的應用程序可以利用 Jazzy 對用戶字典管理的支持,執行向字典增加單詞、忽略單詞、用選中的修正自動替換重復錯誤拼寫等任務。要獲得詳細信息,請參閱 SpellCheckEvent (在 參考資料中)的 API 文檔。
結束語
在撰寫這篇文章的時候,Jazzy API 仍然是一個 alpha 軟件,版本號為 0.5。作為一個相對年輕的 API,Jazzy 的改進和擴展是公開的。對于初學者,Jazzy 更多地表現出相對于它的近親 Aspell 所做的一些改進。如果更進一步的話,Jazzy 對于設計上下文感知或語法感知的拼寫檢查器來說,會是一個理想的框架(使用自然語言處理的一些特性而不是簡單的單詞列表)。
事實上,Jazzy 是穩固的。雖然對于在 Java 平臺上開發拼寫檢查軟件來說仍然是個相對簡單的 API,但是因為 Jazzy 是開放源代碼的,所以任何人都可對它未來的發展做出貢獻。而 API 也可以被用作框架,對其進行擴展后用于內部應用程序開發。請參閱 參考資料一節,了解更多本文所討論的算法,以及 Java 平臺的新拼寫檢查器 API--Jazzy。
?
總結
以上是生活随笔為你收集整理的介绍 Java 平台的 Jazzy:一种新的拼写检查器 API的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如果我要...(开发版)
- 下一篇: Java的重写和重载机制