1. 程式人生 > >ansj分詞原理

ansj分詞原理

    ansj第一步會進行原子切分和全切分,並且是在同時進行的。所謂原子,是指短句中不可分割的最小語素單位。例如,一個漢字就是一個原子。
全切分,就是把一句話中的所有詞都找出來,只要是字典中有的就找出來。例如,“提高中國人生活水平”包含的詞有:提高、高中、中國、國人、人生、生活、活水、水平。接著以“提高中國人生活水平”為例,呼叫ansj標準分詞:

String str = "提高中國人生活水平" ;
Result result = ToAnalysis.parse(str);
System.out.println(result.getTerms());

Analysis類的analysisStr(String temp)會對幾句話進行分詞。先不考慮使用者自定義詞典,直接看這兩幾代碼:

if (startOffe < gp.chars.length ) {
    analysis(gp, startOffe, gp.chars.length);
}

    其中,terms[0]是“提”,terms[0].next是“提高”。由於“提高中”不再是個詞,所以terms[0].next.next是null。類似的,terms[1]是“高”,terms[1].next是“高中”,terms[1].next.next是null。
至於terms[9]為什麼是null,這是因為“水平”是個詞,但可以繼續,比如“水平面”、“水平線”;而且,“平”也可以繼續,比如“評價”、“平凡”。如果把例句換成“提高中國人民生活水平啊”,就不會出現null。這裡先不做深入討論。


    看著一行行程式碼,挺多挺複雜的。真正debug一遍,發現很多程式碼都執行不到。看來有大量的程式碼,是用來處理少數特殊情況的。涉及到的幾個類及基本介紹(只看與本節內容相關的屬性和方法,不然太多了):

1. Analysis
基本分詞+人名識別的一個抽象類。
  (1) analysis(Graph gp, int startOffe, int endOffe)
該方法用於對一句話進行分詞。
對於switch語句switch (status(chars[i])),
case 4:英文字母
case 5:阿拉伯數字或者小數點
以上兩種情況,處理邏輯都比較簡單,重頭戲是default。


在default中,start是本輪分詞的起始位置,end是本輪分詞的終止位置。start和end之間只能是漢子或者標點符號。先下面這幾行核心程式碼:

gwi.setChars(chars, start, end);
while ((str = gwi.allWords()) != null) {
    Term term = new Term(str, gwi.offe, gwi.getItem());
    gp.addTerm(term);
}

    這幾行程式碼就實現了將一句漢語,一個一個地分詞。每分出一個詞,就例項化一個Term,並加入到圖(也就是變數gp)中。例項化Term的引數,str是該詞的漢字表示;gwi.offe是該詞在句子中起始位置的偏移量(這個引數很重要,保證了新的Term可以被插入正確的位置);gwi.getItem()是該詞在字典中的一些資訊。ansj的早期版本,只有上面這幾行程式碼。目前的版本(5.1.2)多了下面這幾行程式碼:

int len = term.getOffe() - max;
if (len > 0) {
    for (; max < term.getOffe();) {
        gp.addTerm(new Term(String.valueOf(chars[max]), max, TermNatures.NULL));
        max++;
    }
}

這是為了強行將不能為詞的單字,插入到terms。我們可以把上面幾行程式碼註釋,然後以“深圳市碧荔花園”為例進行切分,analysis處理後結果如下:

注意上圖中,terms[7]是null。正常情況下,terms[7]應該是荔。荔在核心字典中的資訊如下:
33620 荔 122986 -1 1 null
state是1,也就是說,"荔"不能單字為詞(比如可以組成"荔枝"這個詞)。但是"碧荔花園"是個小區名,"荔"不能為詞,"荔花"根本就不是個詞。這會導致while ((str = gwi.allWords()) != null)這裡獲取分出的詞時,直接跳過“荔”。
上面列出的那幾行程式碼,就是為了解決這種歌特殊情況,解決terms[7]是null的問題。而在後面這段程式碼:

int len = end - max;
if (len > 0) {
    for (; max < end;) {
        gp.addTerm(new Term(String.valueOf(chars[max]), max, TermNatures.NULL));
        max++;
    }
}

解決的是“荔”這種不能為詞的單字,位於句尾的情況。例如“深圳市碧荔花園荔荔荔荔荔”這句話。這印證了我上面說過的那句話吧,有大量的程式碼,是用來處理少數特殊情況的。
2 GetWordsImpl
該類用於從核心字典(core.dic)中獲取詞語。
(1)chars
該屬性是一個char型陣列,儲存了待分詞的句子,如下所示:

2 GetWordsImpl
該類用於從核心字典(core.dic)中獲取詞語。
(1) chars
該屬性是一個char型陣列,儲存了待分詞的句子,如下所示:

(2) offe
該屬性表示當前詞起始位置的偏移量,是public型別的,可用於外部訪問。
例如“深圳市人民政府。”這句話,“深”、“深圳”、“深圳市”三個詞的offe都是0。
與offe對於的,還有可以private型別的start,也是當前詞起始位置的偏移量。當一個詞語結束時,start會比offe多1。
(3) getStatement()
實現了對雙陣列字首樹的查詢。查詢某字或詞在核心字典(core.dic)中的狀態。
0 代表這個字不在詞典中。
1 代表這還不是個詞,需要繼續。例如:102029 如日中 79205 140442 1 null
2 表示這是個詞,但是還可以繼續。例如:96274 囫圇 74746 22251 2 {d=0}
3 表示這已經是個詞了,後面不能繼續了。例如:102819 姍姍來遲 65536 102815 3 {i=2}
其中,標點符號的狀態也是3。
(4) allWords()
根據待分詞的句子(也就是上面提到的chars屬性),一個一個地返回分出的詞語。
for (; i < charsLength; i++)這個for迴圈的i是這個類的屬性,並不是一個臨時變數,從而實現一個一個地返回分出的詞語。
注意這個switch語句:switch (getStatement())
case 0:表示字典中沒有這個詞。這有兩種情況:
1. 這是個單字,直接返回這個單子即可,從下一個位置為起點繼續分詞。
2. 這不是個單子,例如“人生活”這個詞,在字典中是沒有的。這時什麼也不返回,從下一個位置為起點去分詞。
至於遇到“如日中”這種詞,getStatement()返回的是1,switch語句不對這種情況做任何處理,需要接著向後查詢。
3. Graph
該類實現了一個圖(大學時沒好好學圖論,沒想到應用在這裡的)

 

轉自: https://www.cnblogs.com/royhoo/p/6642141.html