介紹 Java 平台的 Jazzy:一種新的拼寫檢查器 APIzzzz

對於包含自然語言文件輸入的應用程式,使用者期望它具備拼寫檢查功能。因為從頭開始建構一個拼寫檢查器不是一項簡單的任務,所以這篇文章為您提供一個使用 Jazzy 的工作區。Jazzy 是一個開放原始程式碼的 Java 拼寫檢查器 API。Java 開發人員 Tom White 對基於電腦拼寫檢查背後的主要演算法進行了深入探討,然後向您展示 Jazzy API 如何才能幫助您把它們整合到 Java 應用程式中。

電腦擅長執行快速搜索操作,可以根據指定的搜索詞,對大量儲存的資訊快速進行搜索。但是,拼寫檢查應用程式所要求的搜索能力,不僅僅是正確的字串匹配。在這篇文章中,我將介紹搜索演算法的一些歷史,包括語音匹配演算法(比如 Soundex 和 Metaphone ),字串相似性類型(例如動態程式設計演算法)。我會解釋這些演算法對於拼寫檢查來說各自的優勢與不足,然後介紹最後一個變種-- Aspell 演算法,這個演算法是專門為拼寫檢查應用程式撰寫的。

Aspell 演算法結合了前面搜索與匹配演算法的最佳特性,是 Jazzy 的底層框架,是 Java 平台的拼寫檢查器 API。在這篇文章的後半部分裡,您將看到 Jazzy 在 Java 框架裡是如何應用 Aspell 演算法的。我會向您展示 Jazzy 識別拼寫錯誤的單字並提供合適的修正 。在文章結尾,我會用一個實例,示範在 Jazzy 的幫助下,您可以很容易地把它的拼寫檢查特性合併到 Java 應用程式中。

語音匹配演算法

正確拼出姓氏可能是個挑戰。如果一個人的姓不太常見,那麼當他們透過電話訂購的時候,經常發現名字被弄錯。即使是常見的名字也可能因為拼寫上的微小差異而拼錯,例如 SmithSmyth是發音相同的常見名字的兩個變體。

特別是在名字拼寫上,變化更豐富,這樣就形成了一些有趣的拼寫檢查演算法。我要介紹的第一個演算法類型是語音匹配演算法,它的目標是解決“哪個名字和聽起來像 x的名字匹配”這樣的問題。在搜索資料庫和其他參考應用程式裡,這種演算法類型相當普遍。例如,在搜索家族歷史時,使用者應該既能檢索到正確匹配,也會得到相似的匹配,這樣就有可能找到家族姓氏淵源流傳中發生變化或者在某些記錄中被拼寫錯誤的歷史。



回到頂端


Soundex 演算法

Soundex 演算法從 1920 年起就一直被用來為所有美國人口做索引,是家族軟體常用的演算法。原始 Soundex 演算法在 1918 年由 Margaret K. Odell 和 Robert C. Russell 申請專利(請參閱 參考資料),他們當初是想“提供一個索引,按照發音而不是按照名字的字母表輸入名字,對名字分組”。

從實際上說,Soundex 演算法的運作方式是把某個字母表中的每個字母對應成代表它的語音組的一個數位程式。在這個方案裡,像 dt)這樣的字母在同一個組裡,因為它們發音相近(實際上每個字母都是用類似的機制發出聲音的),而母音則一概忽略。透過對整體單字應用這種對應,就產生了單字的語音“鍵”。發音相近的單字通常會有相同的鍵。例如, SmithSmyth 的 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 去掉後續的 HW 字母。

  • 行 08 到 11 用字母的語音程式替換單字裡的每個字母。

  • 行 12 刪除相鄰的相同語音程式。(請注意:這意味著,與母音的處理方式不同,插在中間的字元 HW 不會對組合相同程式的字母形成障礙。)

  • 與行 06 類似,行 13 保證單字的第一個字母不變。

  • 行 14 消除所有母音。

  • 行 15 透過把單字裁剪成 4 個字母,形成 Soundex (可能要用字元 0 來填充)。

為了真正理解演算法,手動地逐行執行演算法會很有幫助。程式右手邊的列用於追蹤 word 變數的值,從輸入的名字 Ashcroft開始。對於演算法來說,這是個很好的測試使用案例,因為 sc組合,沒有理會插在中間的 h。(在相同類型的 Web 網站上可以找到的許多 Soundex 實作都沒有正確地實作這一規則。)

Soundex 用於拼寫檢查

不幸的是,Soundex 演算法是一個差勁的拼寫檢查備選方案。首先來說,發音不同的單字可能有相同的 soundex。例如, WhiteWood 的 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),可能有不同的程式。這是由於某些字母可能不發音,例如在 ThompsonT512 )中的 p 造成它發音與 ThomsonT525 )相同,但程式不同,還有 LeighL200 )中的 ghLeeL000 ) ,也有同樣的問題。與此類似,單字的開始字母可能不同,但是不影響它的發音,例如 CarrC600 )中的 cKarrK600 )中的 k。Soundex 演算法本身造成了這個問題,因為它無法把每個單字中的原始字母對應成語音數位。

所謂同音的問題,實際上產生於這樣一個現實:英語語言有不規範拼寫(可能比其他語言更甚)。雖然 Soundex 演算法有許多小的變體,但是他們都缺少對英語拼寫規則的認識,更不用說這些規則的例外了。這種不規範的後果就是, Soundex 不太適合做英語中的拼寫檢查。例如, Soundex 對於拼寫錯誤的 lamL500 ),提供了一個正確拼寫形式 lambL510 )不同的語音撰寫程式。這樣,基於 Soundex 的拼寫檢查應用程式就無法把 lamb作為拼寫錯誤的 lam的修改建議。正是這個問題,引領著 Lawrence Phillips 找到了 Soundex 演算法的替代品,叫做 Metaphone



回到頂端


Metaphone 演算法

Metaphone 演算法背後的想法,首先發表在 1990 年的 Computer Language雜誌上(請參閱 參考資料),這個演算法明確地對英語發音的公共規則進行了撰寫程式,而這正是 Soundex 沒有解決的問題。例如, Metaphone 演算法包含一個明確的規則:在字母 b在單字末尾出現在字母 m後面時,就刪除它。這個規則保證了 lamlamb 會有相同的撰寫程式( 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 指出, BryanBRYN )和 BrianBRN )應該有相同的程式。 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_u44 ? true));
  }
}

雖然 Soundex 和 Metaphone 演算法都解決了語音模糊的匹配問題,但是如果不能糾正打字錯誤,那麼拼寫檢查應用程式是不完整的。當您的手指在鍵盤上滑過,打的是 labmLBM )而不是 lambLM ), 打字錯誤就出現了。語音匹配演算法不能用它的替換來匹配這種拼寫錯誤,因為兩個單字聽起來是不同的。為了解決這類問題,您的拼寫檢查應用程式必須包括字串相似性演算法。



回到頂端


字串相似性演算法

您還記得這樣的字謎嗎--每次只允許修改單字的一個字母,就能把它變換成另外一個單字?例如, ship可以透過逐步修改變成 crow,透過中間單字 shopchopcrop。這種遊戲為您提供了一條路,可以清楚地理解兩個單字之間的距離這一概念。 距離是從一個單字變換成另外一個單字所需要的步數,要求是每次只能改變一個字母,而且每步都要使用字典中實際存在的單字。我把這叫做 字謎距離(puzzle distance)。在這個範例裡, shipcrow之間的字謎距離是 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個字母的成本。如果您以 pzzelpuzzle 的轉換為例,那麼您會得到如 圖 1 所示的網格。


圖1. Levenshtein 演算法的第一階段
Levenshtein 演算法第一階段的表格

接下來,您要計算餘下的每個單格的值,透過考慮它的三個鄰居來計算:上、左、對角上和左。圖 2 顯示了這個計算方案。


圖2:如何計算單格的成本
對角
Min(
對角+ 替換成本,
+ 刪除成本,
+ 插入成本
)

例子結果網格如圖 3 如示。右下角單格的成本是 3,是 pzzelpuzzle之間的 Levenshtein 成本。


圖3. Levenshtein 演算法的最後階段
顯示 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), 其中 mn 分別是原本的單字和目標單字的長度。如果您只需要計算距離,不需要校準,就像通常情況那樣,那麼可以很容易地把空間需求降到 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 演算法就會假定它是拼寫錯誤的。在這種情況下,演算法用以下步驟來建立一個經過排序的修正建議列表:

  1. 加入拼寫錯誤靠近的語音匹配:加入字典中所有與拼寫錯誤單字語音撰寫程式相同的單字, 以及與拼寫錯誤單字的編輯距離小於指定閾值的所有單字。

  2. 加入與拼寫錯誤單字的“靠近遺漏”(near miss)接近的語音匹配:加入與拼寫錯誤單字只差一個編輯操作的所有單字的語音程式。對於這些程式,加入字典中所有與拼寫錯誤單字語音撰寫程式相同的單字, 以及 與拼寫錯誤單字的編輯距離小於指定閾值的單字。

  3. 最佳猜測:如果沒有找到建議,就加入字典中所有與拼寫錯誤的單字的語音程式相同的單字, 以及與拼寫錯誤的單字編輯距離最小的單字。

  4. 排序:按照編輯距離排序單字,把每一步驟中找到的單字放在一起。

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_u101 ?xit): ");
      String line = in.readLine();
      if (line.length() == 0) {
        break;
      }
      spellChecker.checkSpelling(new StringWordTokenizer(line));
    }
  }
}

main() 方法用命令列指定的檔建立了一個 SpellDictionarySpellDictionaryHashMap 實作在記憶體中保存單字,這樣比較快,但是對於大型字典不適合。 (對於容易引起記憶體不足的應用程式,還提供了基於磁片的實作。) 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。




回到頂端


下載

名稱檔案大小下載方式
j-jazzy.zip  FTP
我應該選擇哪種下載方式取得 Adobe® Reader®


回到頂端


參考資料

  • 您可以參閱本文在 developerWorks 全球網站上的 英文原文

  • 按ㄧ下本文頂部或底部的 Code圖示,可以下載本文使用的原始程式碼。

  • Eclipse 外掛 使用 Jazzy 為 Java、JavaScript、Java 屬性檔、XML、HTML、JSP 和 PHP 提供拼寫檢查功能。

  • 由 Margaret K. Odell 和 Robert C. Russell 開發的原始 Soundex 演算法,在 1918 年 獲得專利

  • The Art of Computer Programming, Volume 3, Sorting and Searching(Addison-Wesley,1998),作者:Donald E. Knuth,該書中包含了 Soundex 演算法的權威描述。

  • Lawrence Philips 撰寫的 “Hanging on the Metaphone”( Computer Language,1990年12月)是 Metaphone 演算法的第一次描述。

  • Philips 對 Metaphone 演算法的最佳化發表在這篇文章: “ The Double Metaphone Search Algorithm”( C/C++ Users Journal,2000 年 6 月)。

  • David Crystal 的 The Cambridge Encyclopedia of Language(Cambridge University Press,1997)是目前可以得到的關於世界語言的寶庫。

  • 雖然修正還沒有合併到本文撰寫時的 2.0版,但是 Apache Jakarta Commons Lang 專案的 Chas Emerick 已經提交了一份 解決方案,用於解決在非常大的字串上使用 Levenshtein 距離的問題。

  • 有關 Levenshtein 距離和相關字串距離度量演算法的更多(數學方面的)的細節,請參考 Charras 和 Thierry Lecroq 撰寫的“ Sequence comparison”。

  • 請存取 Aspell 主頁,可以得到 Aspell 演算法的完整指南。

  • 精確字串匹配演算法(King's College London Publications,2004)是一本精確文件搜索相關領域的 偉大著作,作者也是 Christian Charras 和 Thierry Lecroq。

  • 請試用這個 interactive Java applet,計算 Levenshtein 距離並顯示校準。

  • 選擇一個好的 單字清單,與選擇一個好的拼寫檢查演算法同樣重要。

  • 您可能有興趣看看 alphaWorks 的 Dictionary and Thesaurus API for Java

  • WordNet是一個電子語言資料庫,它把英語名詞、動詞、形容詞和副詞組成同義詞集合,每個集合代表一個底層辭彙概念。

  • Efficient text searching in Java”( developerWorks,1999 年 4 月) 研究了 Unicode 撰寫程式的文件搜索演算法。

  • Finding text boundaries in Java”( developerWorks,1999 年 6 月) 討論了在國際化文件中尋找文件邊界的一些微妙之處。

  • 下載 Jazzy,Java 開放原始程式碼的拼寫檢查器

  • 下載 GNU Aspell,免費且開放原始程式碼的拼寫檢查器

  • 下載 Apache Jakarta Commons Codec,這是 Soundex、Metaphone 和 Double Metaphone 的開放原始程式碼實作。

  • 下載 Apache Jakarta Commons Lang,這是 Levenshtein 距離的開放原始程式碼實作。



  • developerWorks Java 技術專區,您可以找到有關 Java 程式設計各個方面的文章。

  • 請存取 Developer Bookstore,獲得技術書籍的完整列表,其中包括數百本 Java 相關主題的書籍。

  • 是否對無需通常的高成本入口點(entry point)或短期評估許可證的 IBM 測試產品感興趣? developerWorks 工具套件訂閱為 WebSphere、DB2、 Lotus、 Rational 和 Tivoli 產品提供了低成本的 12 個月單使用者許可證,包括基於 Eclipse 的 WebSphere Studio IDE,用於開發、測試、評估和展示您的應用程式。


回到頂端


關於作者

Tom White 是 Kizoom 的首席 Java 開發人員。Kizoom 是一家先進的英國軟體公司,提供向移動設備發送個性化旅行資訊的服務。使用者包括英國的國家火車操作員(national train operator)、倫敦公共交通系統,以及英國國家公共汽車公司。自 1999 年成立以來,Kizoom 使用了極限程式設計的所有方法。自 1996 年起,Tom 一直全職撰寫 Java 程式,使用了大部分標準和企業 Java API,撰寫了從使用者 Swing GUI 和圖形到後端訊息傳送系統等各種應用程式。他在劍橋大學獲得了一級榮譽學位(first class honours degree)。工作之餘,Tom 喜歡逗他的小女兒開心,觀看 20 世紀 30 年代的好萊塢電影。可以透過 tom@tiling.org與 Tom 連絡。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现记事本中的拼写检查,可以通过以下步骤来实现: 1. 获取用户输入的文本内容。 2. 将文本内容分割为单词,可以使用 Java 自带的 String 类的 split 方法,指定分隔符为空格。 3. 针对每个单词,使用 Java 自带的 SpellChecker 类中的方法进行拼写检查。可以使用 Jazzy 提供的 SpellChecker 类来实现,这个类提供了多种不同的语言的拼写检查器。 4. 对于拼写错误的单词,可以使用 Java 自带的 JOptionPane 类来弹出提示框,提示用户进行修改。 下面是一个简单的示例代码,可以实现基本的拼写检查功能: ```java import java.util.Locale; import java.util.Scanner; import javax.swing.JOptionPane; import com.swabunga.spell.engine.SpellDictionaryHashMap; import com.swabunga.spell.event.SpellChecker; import com.swabunga.spell.event.StringWordTokenizer; public class SpellCheckDemo { public static void main(String[] args) { // 加载英文拼写词典 SpellDictionaryHashMap dictionary = new SpellDictionaryHashMap( new Scanner(SpellCheckDemo.class.getResourceAsStream("/dictionary/english.0"))); SpellChecker spellChecker = new SpellChecker(dictionary); // 获取用户输入的文本内容 String text = JOptionPane.showInputDialog(null, "请输入文本内容:"); // 将文本内容分割为单词 String[] words = text.split("\\s+"); // 针对每个单词进行拼写检查 for (String word : words) { if (!spellChecker.isCorrect(new StringWordTokenizer(word))) { // 如果拼写错误,弹出提示框 JOptionPane.showMessageDialog(null, "拼写错误:" + word); } } } } ``` 注意,这个示例代码只实现了基本的拼写检查功能,实际使用中还需要考虑更多的细节问题,比如如何处理标点符号、如何处理数字等等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值