zoukankan      html  css  js  c++  java
  • 词干提取算法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源码及自带的注释

  • 相关阅读:
    Mycat学习笔记 第三篇. MySql 主从同步异常后,主从切换
    【转】MYSQL主从同步故障一例及解决过程!
    Mycat学习笔记 第二篇. MySql 读写分离与日志分析——主从多结点
    Mycat学习笔记 第一篇. MySql 读写分离与日志分析——主从单结点
    Leetcode 172 Factorial Trailing Zeroes
    Leetcode 7 Reverse Integer
    Leetcode 2 Add Two Numbers
    Leetcode 83 Remove Duplicates from Sorted List (快慢指针)
    Leetcode 141 Linked List Cycle(快慢指针)
    Leetcode 21 Merge Two Sorted Lists
  • 原文地址:https://www.cnblogs.com/xiaoxiangfeizi/p/2307810.html
Copyright © 2011-2022 走看看