詞干提取算法Porter Stemming Algorithm解讀


      Lucene里面的分詞器里面有一個PorterStemFilter類,里就用到了著名的詞干提取算法。所謂Stemming,就是詞干,在英語中單詞有多種變形。比如單復數加s,進行時加ing等等。在分詞的時候,如果能夠把這些變形單詞的詞根找出了,對搜索結果是很有幫助的。Stemming算法有很多了,三大主流算法是Porter stemming algorithmLovins stemming algorithmLancaster (Paice/Husk) stemming algorithm,還有一些改進的或其它的算法。這個PorterStemFilter里面調用的一個PorterStemmer就是Porter Stemming algorithm的一個實現。 其主頁為http://tartarus.org/~martin/PorterStemmer/,也可查看其論文http://tartarus.org/~martin/PorterStemmer/def.txt。通過以下網頁可以進行簡單的測試:Porter's Stemming Algorithm Online[http://facweb.cs.depaul.edu/mobasher/classes/csc575/porter.html]。

     網上找了好久,才找到一個對此算法解釋的文章,它用的是Java版的代碼,這里我改成用.net版的。主要是把里面的函數作了一下注釋,個人沒做什么分析,本身是想的,結果看着就頭痛。下面的東西都是來自這篇博文波特詞干算法,我只是把這里的代碼改成了.net的。

     接下來,是一系列工具函數。首先先介紹一下它們:

  • cons(i):參數i:int型;返回值bool型。當i為輔音時,返回真;否則為假。
/// <summary>
/// cons(i) 為真 <=> b[i] 是一個輔音 
/// </summary>
private bool cons(int i)
{
    switch (b[i])
    {
        case 'a':
        case 'e':
        case 'i':
        case 'o':
        case 'u':
            return false;
        case 'y':
            return (i == k0) ? true : !cons(i - 1);//y開頭,為輔;否則看i-1位,如果i-1位為輔,y為元,反之亦然。 
        default:
            return true;
    }
}
  • m():返回值:int型。表示單詞b介於0和j之間輔音序列的個度。現假設c代表輔音序列,而v代表元音序列。<..>表示任意存在。於是有如下定義;
    • <c><v>          結果為 0
    • <c>vc<v>       結果為 1
    • <c>vcvc<v>    結果為 2
    • <c>vcvcvc<v> 結果為 3
    • ....
/// <summary>
/// m() 用來計算在0和j之間輔音序列的個數
/// </summary>
/// <returns></returns>
private int m()
{
    int n = 0;//輔音序列的個數,初始化 
    int i = k0;//偏移量 
    while (true)
    {
        if (i > j)//如果超出最大偏移量,直接返回n 
            return n;
        if (!cons(i))//如果是元音,中斷 
            break;
        i++;//輔音移一位,直到元音的位置 
    }
    i++;//移完輔音,從元音的第一個字符開始 
    while (true)//循環計算vc的個數 
    {
        while (true)//循環判斷v 
        {
            if (i > j)
                return n;
            if (cons(i))
                break;//出現輔音則終止循環 
            i++;
        }
        i++;
        n++;
        while (true)//循環判斷c 
        {
            if (i > j)
                return n;
            if (!cons(i))
                break;
            i++;
        }
        i++;
    }
}
  • vowelinstem():返回值:bool型。從名字就可以看得出來,表示單詞b介於0到i之間是否存在元音。
/// <summary>
///  vowelinstem() 為真 <=> 0,...j 包含一個元音 
/// </summary>
/// <returns>[To be supplied.]</returns>
private bool vowelinstem()
{
    int i;
    for (i = k0; i <= j; i++)
        if (!cons(i))
            return true;
    return false;
}
  • doublec(j):參數j:int型;返回值bool型。這個函數用來表示在j和j-1位置上的兩個字符是否是相同的輔音。
/// <summary>
///  doublec(j) 為真 <=> j,(j-1) 包含兩個一樣的輔音 
/// </summary>
/// <param name="j"></param>
/// <returns></returns>
private bool doublec(int j)
{
    if (j < k0 + 1)
        return false;
    if (b[j] != b[j - 1])
        return false;
    return cons(j);
}
  • cvc(i):參數i:int型;返回值bool型。對於i,i-1,i-2位置上的字符,它們是“輔音-元音-輔音”的形式,並且對於第二個輔音,它不能為w、x、y中的一個。這個函數用來處理以e結尾的短單詞。比如說cav(e),lov(e),hop(e),crim(e)。但是像snow,box,tray就輔符合條件。
/* cvc(i) is 為真 <=> i-2,i-1,i 
 * 有形式: 輔音 - 元音 - 輔音   
 * 並且第二個c不是 w,x 或者 y. 
 * 這個用來處理以e結尾的短單詞。
 * e.g.      cav(e), lov(e), hop(e), crim(e), 
 * 但不是    snow, box, tray.   */
private bool cvc(int i)
{
    if (i < k0 + 2 || !cons(i) || cons(i - 1) || !cons(i - 2))
        return false;
    else
    {
        int ch = b[i];
        if (ch == 'w' || ch == 'x' || ch == 'y') return false;
    }
    return true;
}
  • ends(s):參數:String;返回值:bool型。顧名思義,判斷b是否以s結尾。
private bool ends(string s)
{
    int l = s.Length;
    int o = k - l + 1;
    if (o < k0)
        return false;
    for (int i = 0; i < l; i++)
        if (b[o + i] != s[i])
            return false;
    j = k - l;
    return true;
}
  • setto(s):參數:String;void類型。把b在(j+1)...k位置上的字符設為s,同時,調整k的大小。
// setto(s) 設置 (j+1),...k 到s字符串上的字符, 並且調整k值 
 void setto(string s)
 {
     int l = s.Length;
     int o = j + 1;
     for (int i = 0; i < l; i++)
         b[o + i] = s[i];
     k = j + l;
     dirty = true;
 }
  • r(s):參數:String;void類型。在m()>0的情況下,調用setto(s)。
void r(string s) { if (m() > 0) setto(s); }

接下來,就是分六步來進行處理的過程。

第一步,處理復數,以及ed和ing結束的單詞。

private void step1()
{
    if (b[k] == 's')
    {
        if (ends("sses")) k -= 2;//以“sses結尾” 
        else if (ends("ies")) setto("i");//以ies結尾,置為i 
        else if (b[k - 1] != 's') k--;//兩個s結尾不處理 
    }
    if (ends("eed"))//以“eed”結尾,當m>0時,左移一位 
    {
        if (m() > 0)
            k--;
    }
    else if ((ends("ed") || ends("ing")) && vowelinstem())
    {
        k = j;
        if (ends("at")) setto("ate");
        else if (ends("bl")) setto("ble");
        else if (ends("iz")) setto("ize");
        else if (doublec(k))//如果有兩個相同輔音 
        {
            int ch = b[k--];
            if (ch == 'l' || ch == 's' || ch == 'z')
                k++;
        }
        else if (m() == 1 && cvc(k))
            setto("e");
    }
}

第二步,如果單詞中包含元音,並且以y結尾,將y改為i。代碼很簡單:

//如果單詞中包含元音,並且以y結尾,將y改為i
 private void step2()
 {
     if (ends("y") && vowelinstem())
     {
         b[k] = 'i';
         dirty = true;
     }
 }

第三步,將雙后綴的單詞映射為單后綴。

/* step3() 將雙后綴的單詞映射為單后綴。 
 * 所以 -ization ( = -ize 加上    -ation) 被映射到 -ize 等等。
 * 注意在去除后綴之前必須確保    m() > 0. */
private void step3()
{
    if (k == k0) return; /* For Bug 1 */
    switch (b[k - 1])
    {
        case 'a':
            if (ends("ational")) { r("ate"); break; }
            if (ends("tional")) { r("tion"); break; }
            break;
        case 'c':
            if (ends("enci")) { r("ence"); break; }
            if (ends("anci")) { r("ance"); break; }
            break;
        case 'e':
            if (ends("izer")) { r("ize"); break; }
            break;
        case 'l':
            if (ends("bli")) { r("ble"); break; }
            if (ends("alli")) { r("al"); break; }
            if (ends("entli")) { r("ent"); break; }
            if (ends("eli")) { r("e"); break; }
            if (ends("ousli")) { r("ous"); break; }
            break;
        case 'o':
            if (ends("ization")) { r("ize"); break; }
            if (ends("ation")) { r("ate"); break; }
            if (ends("ator")) { r("ate"); break; }
            break;
        case 's':
            if (ends("alism")) { r("al"); break; }
            if (ends("iveness")) { r("ive"); break; }
            if (ends("fulness")) { r("ful"); break; }
            if (ends("ousness")) { r("ous"); break; }
            break;
        case 't':
            if (ends("aliti")) { r("al"); break; }
            if (ends("iviti")) { r("ive"); break; }
            if (ends("biliti")) { r("ble"); break; }
            break;
        case 'g':
            if (ends("logi")) { r("log"); break; }
            break;
    }
}

第四步,處理-ic-,-full,-ness等等后綴。和步驟3有着類似的處理。

/* step4() deals with -ic-, -full, -ness etc. similar strategy to step3. */
//處理-ic-,-full,-ness等等后綴。和步驟3有着類似的處理。
private void step4()
{
    switch (b[k])
    {
        case 'e':
            if (ends("icate")) { r("ic"); break; }
            if (ends("ative")) { r(""); break; }
            if (ends("alize")) { r("al"); break; }
            break;
        case 'i':
            if (ends("iciti")) { r("ic"); break; }
            break;
        case 'l':
            if (ends("ical")) { r("ic"); break; }
            if (ends("ful")) { r(""); break; }
            break;
        case 's':
            if (ends("ness")) { r(""); break; }
            break;
    }
}

第五步,在<c>vcvc<v>情形下,去除-ant,-ence等后綴。

//step5() takes off -ant, -ence etc., in context <c>vcvc<v>. 
//在<c>vcvc<v>情形下,去除-ant,-ence等后綴。
private void step5()
{
    if (k == k0) return; /* for Bug 1 */
    switch (b[k - 1])
    {
        case 'a':
            if (ends("al")) break;
            return;
        case 'c':
            if (ends("ance")) break;
            if (ends("ence")) break;
            return;
        case 'e':
            if (ends("er")) break; return;
        case 'i':
            if (ends("ic")) break; return;
        case 'l':
            if (ends("able")) break;
            if (ends("ible")) break; return;
        case 'n':
            if (ends("ant")) break;
            if (ends("ement")) break;
            if (ends("ment")) break;
            /* element etc. not stripped before the m */
            if (ends("ent")) break;
            return;
        case 'o':
            if (ends("ion") && j >= 0 && (b[j] == 's' || b[j] == 't')) break;
            /* j >= 0 fixes Bug 2 */
            if (ends("ou")) break;
            return;
        /* takes care of -ous */
        case 's':
            if (ends("ism")) break;
            return;
        case 't':
            if (ends("ate")) break;
            if (ends("iti")) break;
            return;
        case 'u':
            if (ends("ous")) break;
            return;
        case 'v':
            if (ends("ive")) break;
            return;
        case 'z':
            if (ends("ize")) break;
            return;
        default:
            return;
    }
    if (m() > 1)
        k = j;
}

第六步,也就是最后一步,在m()>1的情況下,移除末尾的“e”。

// step6() removes a final -e if m() > 1. 
//也就是最后一步,在m()>1的情況下,移除末尾的“e”。
private void step6()
{
    j = k;
    if (b[k] == 'e')
    {
        int a = m();
        if (a > 1 || a == 1 && !cvc(k - 1))
            k--;
    }
    if (b[k] == 'l' && doublec(k) && m() > 1)
        k--;
}

在了解了步驟之后,我們寫一個stem()方法,來完成得到詞干的工作。

public bool stem(int i0)
{
    k = i - 1;
    k0 = i0;
    if (k > k0 + 1)
    {
        step1(); step2(); step3(); step4(); step5(); step6();
    }
    // Also, a word is considered dirty if we lopped off letters
    // Thanks to Ifigenia Vairelles for pointing this out.
    if (i != k + 1)
        dirty = true;
    i = k + 1;
    return dirty;
}

最后要提醒的就是,傳入的單詞必須是小寫。關於Porter Stemmer的實現就是這些.

需要測試數據這里是樣本文件。而相應的輸出文件在這里。更多內容請參考官方網站。

另外,波特詞干算法有第二個版本,它的處理結果要比文中所介紹的算法准確度高,但是,相應地也就更復雜,消耗的時間也就更多。本文就不作解釋,詳細參考官方網站The Porter2 stemming algorithm

這里有一個關於此算法的應用:WordCloud - A Squarified Treemap of Word Frequency

以上的解釋轉自前面所說的博客,你可以在本文最后的參考資料中找到鏈接.

這是整個PorterStemmer類的代碼:

public class PorterStemmer
    {
        private char[] b;
        private int i,    /* offset into b */
            j, k, k0;
        private bool dirty = false;
        private static int INC = 50; /* unit of size whereby b is increased */
        private static int EXTRA = 1;

        /// 
        /// Initializes a new instance of the PorterStemmer class.
        /// 
        public PorterStemmer()
        {
            b = new char[INC];
            i = 0;
        }

        /// 
        /// reset() resets the stemmer so it can stem another word.  If you invoke
        /// the stemmer by calling add(char) and then stem(), you must call reset()
        /// before starting another word.
        ///
        public void reset() { i = 0; dirty = false; }

        ///
        /// Add a character to the word being stemmed.  When you are finished 
        /// adding characters, you can call stem(void) to process the word. 
        ///
        public void add(char ch)
        {
            if (b.Length <= i + EXTRA)
            {
                char[] new_b = new char[b.Length + INC];
                for (int c = 0; c < b.Length; c++)
                    new_b[c] = b[c];
                b = new_b;
            }
            b[i++] = ch;
        }

        ///
        /// After a word has been stemmed, it can be retrieved by toString(), 
        /// or a reference to the internal buffer can be retrieved by getResultBuffer
        /// and getResultLength (which is generally more efficient.)
        ///
        public string toString() { return new string(b, 0, i); }

        ///
        /// Returns the length of the word resulting from the stemming process.
        ///
        public int getResultLength() { return i; }

        ///
        /// Returns a reference to a character buffer containing the results of
        /// the stemming process.  You also need to consult getResultLength()
        /// to determine the length of the result.
        ///
        public char[] getResultBuffer() { return b; }

        /// 
        /// cons(i) is true <=> b[i] is a consonant.
        /// cons(i) 為真 <=> b[i] 是一個輔音 
        /// 
        /// The input parameter.
        /// True or False.
        private bool cons(int i)
        {
            switch (b[i])
            {
                case 'a':
                case 'e':
                case 'i':
                case 'o':
                case 'u':
                    return false;
                case 'y':
                    return (i == k0) ? true : !cons(i - 1);//y開頭,為輔;否則看i-1位,如果i-1位為輔,y為元,反之亦然。 
                default:
                    return true;
            }
        }

        /// 
        /// m() 用來計算在0和j之間輔音序列的個數
        /// m() measures the number of consonant sequences between k0 and j. if c is
        ///   a consonant sequence and v a vowel sequence, and <..> indicates arbitrary
        ///   presence,
        ///
        ///		       gives 0
        ///		vc     gives 1
        ///		vcvc   gives 2
        ///		vcvcvc gives 3
        ///		....
        /// 
        /// 
        private int m()
        {
            int n = 0;//輔音序列的個數,初始化 
            int i = k0;//偏移量 
            while (true)
            {
                if (i > j)//如果超出最大偏移量,直接返回n 
                    return n;
                if (!cons(i))//如果是元音,中斷 
                    break;
                i++;//輔音移一位,直到元音的位置 
            }
            i++;//移完輔音,從元音的第一個字符開始 
            while (true)//循環計算vc的個數 
            {
                while (true)//循環判斷v 
                {
                    if (i > j)
                        return n;
                    if (cons(i))
                        break;//出現輔音則終止循環 
                    i++;
                }
                i++;
                n++;
                while (true)//循環判斷c 
                {
                    if (i > j)
                        return n;
                    if (!cons(i))
                        break;
                    i++;
                }
                i++;
            }
        }

        /// 
        /// vowelinstem() is true <=> k0,...j contains a vowel
        ///  vowelinstem() 為真 <=> 0,...j 包含一個元音 
        /// 
        /// [To be supplied.]
        private bool vowelinstem()
        {
            int i;
            for (i = k0; i <= j; i++)
                if (!cons(i))
                    return true;
            return false;
        }

        /// 
        /// doublec(j) is true <=> j,(j-1) contain a double consonant.
        ///  doublec(j) 為真 <=> j,(j-1) 包含兩個一樣的輔音 
        /// 
        /// 
        /// 
        private bool doublec(int j)
        {
            if (j < k0 + 1)
                return false;
            if (b[j] != b[j - 1])
                return false;
            return cons(j);
        }

        /* cvc(i) is true <=> i-2,i-1,i has the form consonant - vowel - consonant
           and also if the second c is not w,x or y. this is used when trying to
           restore an e at the end of a short word. e.g.

                cav(e), lov(e), hop(e), crim(e), but
                snow, box, tray.

        */
        /* cvc(i) is 為真 <=> i-2,i-1,i 
         * 有形式: 輔音 - 元音 - 輔音   
         * 並且第二個c不是 w,x 或者 y. 
         * 這個用來處理以e結尾的短單詞。
         * e.g.      cav(e), lov(e), hop(e), crim(e), 
         * 但不是    snow, box, tray.   */
        private bool cvc(int i)
        {
            if (i < k0 + 2 || !cons(i) || cons(i - 1) || !cons(i - 2))
                return false;
            else
            {
                int ch = b[i];
                if (ch == 'w' || ch == 'x' || ch == 'y') return false;
            }
            return true;
        }

        private bool ends(string s)
        {
            int l = s.Length;
            int o = k - l + 1;
            if (o < k0)
                return false;
            for (int i = 0; i < l; i++)
                if (b[o + i] != s[i])
                    return false;
            j = k - l;
            return true;
        }

        /* setto(s) sets (j+1),...k to the characters in the string s, readjusting
           k. */
        // setto(s) 設置 (j+1),...k 到s字符串上的字符, 並且調整k值 
        void setto(string s)
        {
            int l = s.Length;
            int o = j + 1;
            for (int i = 0; i < l; i++)
                b[o + i] = s[i];
            k = j + l;
            dirty = true;
        }

        /* r(s) is used further down. */

        void r(string s) { if (m() > 0) setto(s); }

        /* step1() gets rid of plurals and -ed or -ing. e.g.
          處理復數,ed或者ing結束的單詞。比如:

                 caresses  ->  caress
                 ponies    ->  poni
                 ties      ->  ti
                 caress    ->  caress
                 cats      ->  cat

                 feed      ->  feed
                 agreed    ->  agree
                 disabled  ->  disable

                 matting   ->  mat
                 mating    ->  mate
                 meeting   ->  meet
                 milling   ->  mill
                 messing   ->  mess

                 meetings  ->  meet

        */

        private void step1()
        {
            if (b[k] == 's')
            {
                if (ends("sses")) k -= 2;//以“sses結尾” 
                else if (ends("ies")) setto("i");//以ies結尾,置為i 
                else if (b[k - 1] != 's') k--;//兩個s結尾不處理 
            }
            if (ends("eed"))//以“eed”結尾,當m>0時,左移一位 
            {
                if (m() > 0)
                    k--;
            }
            else if ((ends("ed") || ends("ing")) && vowelinstem())
            {
                k = j;
                if (ends("at")) setto("ate");
                else if (ends("bl")) setto("ble");
                else if (ends("iz")) setto("ize");
                else if (doublec(k))//如果有兩個相同輔音 
                {
                    int ch = b[k--];
                    if (ch == 'l' || ch == 's' || ch == 'z')
                        k++;
                }
                else if (m() == 1 && cvc(k))
                    setto("e");
            }
        }

        /* step2() turns terminal y to i when there is another vowel in the stem. */
        //如果單詞中包含元音,並且以y結尾,將y改為i
        private void step2()
        {
            if (ends("y") && vowelinstem())
            {
                b[k] = 'i';
                dirty = true;
            }
        }

        /* step3() maps double suffices to single ones. so -ization ( = -ize plus
           -ation) maps to -ize etc. note that the string before the suffix must give
           m() > 0. */
        /* step3() 將雙后綴的單詞映射為單后綴。 
         * 所以 -ization ( = -ize 加上    -ation) 被映射到 -ize 等等。
         * 注意在去除后綴之前必須確保    m() > 0. */
        private void step3()
        {
            if (k == k0) return; /* For Bug 1 */
            switch (b[k - 1])
            {
                case 'a':
                    if (ends("ational")) { r("ate"); break; }
                    if (ends("tional")) { r("tion"); break; }
                    break;
                case 'c':
                    if (ends("enci")) { r("ence"); break; }
                    if (ends("anci")) { r("ance"); break; }
                    break;
                case 'e':
                    if (ends("izer")) { r("ize"); break; }
                    break;
                case 'l':
                    if (ends("bli")) { r("ble"); break; }
                    if (ends("alli")) { r("al"); break; }
                    if (ends("entli")) { r("ent"); break; }
                    if (ends("eli")) { r("e"); break; }
                    if (ends("ousli")) { r("ous"); break; }
                    break;
                case 'o':
                    if (ends("ization")) { r("ize"); break; }
                    if (ends("ation")) { r("ate"); break; }
                    if (ends("ator")) { r("ate"); break; }
                    break;
                case 's':
                    if (ends("alism")) { r("al"); break; }
                    if (ends("iveness")) { r("ive"); break; }
                    if (ends("fulness")) { r("ful"); break; }
                    if (ends("ousness")) { r("ous"); break; }
                    break;
                case 't':
                    if (ends("aliti")) { r("al"); break; }
                    if (ends("iviti")) { r("ive"); break; }
                    if (ends("biliti")) { r("ble"); break; }
                    break;
                case 'g':
                    if (ends("logi")) { r("log"); break; }
                    break;
            }
        }

        /* step4() deals with -ic-, -full, -ness etc. similar strategy to step3. */
        //處理-ic-,-full,-ness等等后綴。和步驟3有着類似的處理。
        private void step4()
        {
            switch (b[k])
            {
                case 'e':
                    if (ends("icate")) { r("ic"); break; }
                    if (ends("ative")) { r(""); break; }
                    if (ends("alize")) { r("al"); break; }
                    break;
                case 'i':
                    if (ends("iciti")) { r("ic"); break; }
                    break;
                case 'l':
                    if (ends("ical")) { r("ic"); break; }
                    if (ends("ful")) { r(""); break; }
                    break;
                case 's':
                    if (ends("ness")) { r(""); break; }
                    break;
            }
        }

        /* step5() takes off -ant, -ence etc., in context vcvc. */
        //在vcvc情形下,去除-ant,-ence等后綴。
        private void step5()
        {
            if (k == k0) return; /* for Bug 1 */
            switch (b[k - 1])
            {
                case 'a':
                    if (ends("al")) break;
                    return;
                case 'c':
                    if (ends("ance")) break;
                    if (ends("ence")) break;
                    return;
                case 'e':
                    if (ends("er")) break; return;
                case 'i':
                    if (ends("ic")) break; return;
                case 'l':
                    if (ends("able")) break;
                    if (ends("ible")) break; return;
                case 'n':
                    if (ends("ant")) break;
                    if (ends("ement")) break;
                    if (ends("ment")) break;
                    /* element etc. not stripped before the m */
                    if (ends("ent")) break;
                    return;
                case 'o':
                    if (ends("ion") && j >= 0 && (b[j] == 's' || b[j] == 't')) break;
                    /* j >= 0 fixes Bug 2 */
                    if (ends("ou")) break;
                    return;
                /* takes care of -ous */
                case 's':
                    if (ends("ism")) break;
                    return;
                case 't':
                    if (ends("ate")) break;
                    if (ends("iti")) break;
                    return;
                case 'u':
                    if (ends("ous")) break;
                    return;
                case 'v':
                    if (ends("ive")) break;
                    return;
                case 'z':
                    if (ends("ize")) break;
                    return;
                default:
                    return;
            }
            if (m() > 1)
                k = j;
        }

        // step6() removes a final -e if m() > 1. 
        //也就是最后一步,在m()>1的情況下,移除末尾的“e”。
        private void step6()
        {
            j = k;
            if (b[k] == 'e')
            {
                int a = m();
                if (a > 1 || a == 1 && !cvc(k - 1))
                    k--;
            }
            if (b[k] == 'l' && doublec(k) && m() > 1)
                k--;
        }


        /// 
        /// Stem a word provided as a string.  Returns the result as a string.
        ///
        public string stem(string s)
        {
            if (stem(s.ToCharArray(), s.Length))
                return toString();
            else
                return s;
        }

        /// Stem a word contained in a char[].  Returns true if the stemming process
        /// resulted in a word different from the input.  You can retrieve the 
        /// result with getResultLength()/getResultBuffer() or toString(). 
        ///
        public bool stem(char[] word)
        {
            return stem(word, word.Length);
        }

        /// Stem a word contained in a portion of a char[] array.  Returns
        /// true if the stemming process resulted in a word different from
        /// the input.  You can retrieve the result with
        /// getResultLength()/getResultBuffer() or toString().  
        ///
        public bool stem(char[] wordBuffer, int offset, int wordLen)
        {
            reset();
            if (b.Length < wordLen)
            {
                char[] new_b = new char[wordLen + EXTRA];
                b = new_b;
            }
            for (int j = 0; j < wordLen; j++)
                b[j] = wordBuffer[offset + j];
            i = wordLen;
            return stem(0);
        }

        /// Stem a word contained in a leading portion of a char[] array.
        /// Returns true if the stemming process resulted in a word different
        /// from the input.  You can retrieve the result with
        /// getResultLength()/getResultBuffer() or toString().  
        ///
        public bool stem(char[] word, int wordLen)
        {
            return stem(word, 0, wordLen);
        }

        /// Stem the word placed into the Stemmer buffer through calls to add().
        /// Returns true if the stemming process resulted in a word different
        /// from the input.  You can retrieve the result with
        /// getResultLength()/getResultBuffer() or toString().  
        ///
        public bool stem()
        {
            return stem(0);
        }

        /// 
        /// [To be supplied.]
        /// 
        /// [To be supplied.]
        /// [To be supplied.]
        public bool stem(int i0)
        {
            k = i - 1;
            k0 = i0;
            if (k > k0 + 1)
            {
                step1(); step2(); step3(); step4(); step5(); step6();
            }
            // Also, a word is considered dirty if we lopped off letters
            // Thanks to Ifigenia Vairelles for pointing this out.
            if (i != k + 1)
                dirty = true;
            i = k + 1;
            return dirty;
        }

        /// Test program for demonstrating the Stemmer.  It reads a file and
        /// stems each word, writing the result to standard out.  
        /// Usage: Stemmer file-name 
        ///
        public static void Main(string[] args)
        {
            PorterStemmer s = new PorterStemmer();

            for (int i = 0; i < args.Length; i++)
            {
                try
                {
                    FileStream fs = new FileStream(args[i], FileMode.Open);
                    byte[] buffer = new byte[1024];
                    int bufferLen, offset, ch;

                    bufferLen = fs.Read(buffer, 0, buffer.Length);
                    offset = 0;
                    s.reset();

                    while (true)
                    {
                        if (offset < bufferLen)
                            ch = buffer[offset++];
                        else
                        {
                            bufferLen = fs.Read(buffer, 0, buffer.Length);
                            offset = 0;
                            if (bufferLen < 0)
                                ch = -1;
                            else
                                ch = buffer[offset++];
                        }

                        if (Char.IsLetter((char)ch))
                        {
                            s.add(Char.ToLower((char)ch));
                        }
                        else
                        {
                            s.stem();
                            Console.Write(s.toString());
                            s.reset();
                            if (ch < 0)
                                break;
                            else
                            {
                                Console.Write((char)ch);
                            }
                        }
                    }

                    fs.Close();
                }
                catch (IOException e)
                {
                    Console.WriteLine("error reading " + args[i], e);
                }
            }
        }
    }

參考資料:

1.Porter stemming algorithm

2.波特詞干算法

3.Lucene源碼及自帶的注釋


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM