Jazzy--一種新的拼寫檢查器API(Java平臺)
計算機擅長執行快速搜尋操作,可以根據給定的搜尋詞,對大量儲存的資訊快速進行搜尋。但是,拼寫檢查應用程式所要求的搜尋能力,不僅僅是正確的字串匹配。
在這篇文章中,我將介紹搜尋演算法的一些歷史,包括語音匹配演算法(比如 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。
相關推薦
Jazzy--一種新的拼寫檢查器API(Java平臺)
計算機擅長執行快速搜尋操作,可以根據給定的搜尋詞,對大量儲存的資訊快速進行搜尋。但是,拼寫檢查應用程式所要求的搜尋能力,不僅僅是正確的字串匹配。 在這篇文章中,我將介紹搜尋演算法的一些歷史,包括語音匹配演算法(比如 Soundex 和 Metaphone
一種拼音分詞器的JAVA實現
原理很簡單,就是模式匹配。根據中文全拼的特點,即聲母和韻母配對,首先列舉出所有的聲母,再分別列舉出所有聲母對應的韻母集,分詞的過程就是遍歷匹配的過程。 publicclass SpellTool { static String result = "";//
SaltStack介紹——SaltStack是一種新的基礎設施管理方法開發軟件,簡單易部署,可伸縮的足以管理成千上萬的服務器,和足夠快的速度控制,與他們交流
con mar stack 通信 class 交流 ast 集中 速度 SaltStack介紹和架構解析 簡介 SaltStack是一種新的基礎設施管理方法開發軟件,簡單易部署,可伸縮的足以管理成千上萬的服務器,和足夠快的速度控制,與他們交流,以毫秒為單位。S
一種新的進入容器的方式: WebSocket + Docker Remote API
眾所周知,容器是基於作業系統核心的一種輕量級的虛擬化技術。其可以類比於虛擬機器,但其本身並不是虛擬機器。在傳統的虛擬機器使用場景中,每個使用者都會通過堡壘機,根據自己被分配的許可權,登入某些機器的某些賬號。當應用部署逐漸轉移到基於容器技術的PaaS平臺上後,讓使用者進入容器進行觀察、除錯應用已經成
kotlin, 一種新的android平臺一級開發語言
程序 16px 語法 jvm ava lin 使用 ide 擁有 最近看到一則科技新聞, 大致內容是google將kotlin語言作為android應用開發的一級語言, 與java並駕齊驅, 這是一個開發界的大事件大新聞, 連google的親兒子go語言也沒有這
【HLSDK系列】怎麽增加一種新實體
sta fun class 使用 pen 關聯 creat bsp cnblogs 你平常肯定接觸到很多比如 info_player_start hostage info_target 之類的實體,這裏就解釋一下怎麽創建一種新的實體。 首先建立一個新的 .h 文件(當然你寫
SpringBank 開發日誌 一種簡單的攔截器設計實現
exp bst 一個 pin factory span 之前 system request 當交易由Action進入Service之前,需要根據不同的Service實際負責業務的不同,真正執行Service的業務邏輯之前,做一些檢查工作。這樣的攔截器應該是基於配置的,與Se
QProcess::startDetached(5.10有了一種新的方式)
add rep set rpath pos ppr sed int rtai From Qt 5.10 on, there is a new way how to start detached processes with QProcess. Of course you
貝葉斯拼寫檢查器
ida 貝葉斯 read alter lower AD rect open altera 本拼寫檢查器是基於樸素貝葉斯的基礎來寫的,貝葉斯公式以及原理就不在詳述。直接上代碼 import re, collections def words(text): re
用貝葉斯實現拼寫檢查器
alt rec lam 最終 findall features 判斷 edit correct 貝葉斯公式 p(A|D)=p(A)*p(D|A)/p(D); 可以應用於垃圾郵件的過濾和拼寫檢查 例如:對於拼寫檢查,寫出一個單詞D,判斷該單詞為正確單詞A的概率。為上述條件概率
VMware vSAN中小企業應用案例,嘗試一種新的教學方式
1年 配置 vsphere 掌握 blog 編輯 初學者 延伸 ima 各位朋友,大家好!我是王春海,很高興你能閱讀我寫的文章。許多朋友知道我,可能是看我的博客,可能是看我的文章,或者看我寫作出版的圖書。你們的認可,是我繼續學習、持續創作的動力! 這些年我寫的一些文章主要
一種煙霧傳感器的光電式迷宮老化探測數據自動校正技術
環境 改變 技術 集中 如何 沒有 alt 娛樂 font 2018-11-5 俗稱“迷宮老化自動校正算法”。 煙霧探測器檢測環境煙霧濃度的原理是什麽? 光電式迷宮煙感檢測環境煙霧濃度的原理是通過一種“光電式傳感器迷宮”反饋的數值來表現的。 圖1 光電式煙霧
從實驗室走向世界:HSP90抑制劑,一種新的癌症藥物
熱休克蛋白90(HSP90)是細胞內一種普遍存在的、十分保守的以及有高度活性的蛋白質,它在腫瘤細胞內的含量要比正常細胞多。作為一種分子伴侶,HSP90協助不同種類的癌蛋白(即HSP90的服務蛋白)進行摺疊並使之變得穩定、成熟,而HSP90的服務蛋白中擁有大量的諸如激酶和轉錄因子等訊號轉導分子,這些分子對於腫瘤
一種新的python區域性除錯手法
我們都知道,python裡面可以用pdb來除錯程式碼。但是pdb往往不大好用。有時候除錯程式碼往往在多重條件裡面,直接用pdb需要下條件斷點,設定複雜的條件。 一個簡單的辦法就是這麼幹。 __import__('pdb').set_trace() 但是有的時候,連這個出現的條件都不滿足。例如,程式碼必須
.net 一種新的傳參方式作為傳參的參考,很可能在實際專案中使用
<asp:LinkButton ID="LinkButton1" runat="server" OnClientClick='<%# String.Format("return checkreturn(\"turnpost\",\"{0}
為TexWorks新增字典和拼寫檢查器
[TOC] 1 介紹 任何一個用於書寫文件的軟體,拼寫檢查一直以來是一個重要的功能。人在書寫各種資料時難免會書寫一些拼寫錯誤的單詞,靠人工檢查拼寫錯誤是費時費力的,同時也容易漏判,因此為軟體配置拼寫檢查功能非常重要。 MS Word、OpenOffice等軟體都配備有拼寫檢查
Filenet:ipfs網路激勵層一種新的解決方案
人有不當好人的權利,可是如果別人想當好人,我們起碼不要去做洩氣的旁觀者。 ----考慮到Filecoin在當下的影響,中立的表達對新激勵層的看法 今年慢熊市,大家對挖礦都失去了信心,不過有兩樣東西是共識的熱點,一是EOS超級節點,另一個就是IPFS。 我們知道,第一批挖
一種新的自動化 UI 測試解決方案 Airtest Project
今天分享一個自動化UI測試工具airtest——一款網易出品的基於影象識別面向遊UI測試的工具,也支援原生Android App基於元素識別的UI自動化測試。主要包含了三部分:Airtest IDE、Airtest(用截圖寫指令碼)和 Poco(用介面UI元素來寫指令碼)。 來自google的評價:
for迴圈的一種新的使用方法
在使用Qt過程中,看到有人使用一種新的for迴圈,感覺很方便,就記錄下來了。 vector<QString> vStrList; vStrList.push_back("A"); vStrList.push_back("B"); vStrLis
Appium在ios下獲取頁面元素的一種新思路
p.p1 { margin: 0.0px 0.0px 16.5px 0.0px; text-align: justify; font: 22.0px "PingFang SC Semibold"; color: #000000 } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px;