11# -*- coding: utf-8 -*-
2- from __future__ import absolute_import ,unicode_literals
3- '''
4- โปรแกรม multi-cut
5- ตัดคำภาษาไทยโดยใช้ Maximum Matching algorithm
6- เดติดโค้ดต้นฉบับ คุณ Korakot Chaovavanich
7- จาก https://www.facebook.com/groups/408004796247683/permalink/431283740586455/
8- และ https://gist.github.com/korakot/fe26c65dc9eed467f4497f784a805716
2+ '''ตัวตัดคำภาษาไทยโดยใช้หลักการ maximal matching และ TCC
3+ พัฒนาโดยคุณ Korakot Chaovavanich
4+ Notebook : https://colab.research.google.com/notebook#fileId=1V1Z657_5eSWPo8rLfVRwA0A5E4vkg7SI
95'''
10- import six
11- if six .PY2 :
12- from builtins import *
6+ from __future__ import absolute_import ,unicode_literals
137import re
14- import copy
15- from pythainlp .tools import file_trie
16- from marisa_trie import Trie
178from collections import defaultdict
18- from pythainlp .tokenize import tcc
19- class LatticeString (str ):
20- ''' String subclass เพื่อเก็บวิธีตัดหลายๆ วิธี
21- '''
22- def __new__ (cls , value , multi = None , in_dict = True ):
23- return str .__new__ (cls , value )
9+ from heapq import heappush , heappop # for priority queue
10+ from marisa_trie import Trie
11+ from pythainlp .corpus .thaiword import get_data # ดึงข้อมูลรายการคำในภาษาไทย
2412
25- def __init__ (self , value , multi = None , in_dict = True ):
26- self .unique = True
27- if multi :
28- self .multi = list (multi )
29- if len (self .multi ) > 1 :
30- self .unique = False
31- else :
32- self .multi = [value ]
33- self .in_dict = in_dict # บอกว่าเป็นคำมีในดิกหรือเปล่า
34- spat_eng = r'''(?x)
35- ([\d,\.]\#)+| # number
36- ([๑๒๓๔๕๖๗๘๙๐,\.]\#)+| # thai number
37- ([a-zA-Z]\#)+| # english
13+
14+ # ช่วยตัดพวกภาษาอังกฤษ เป็นต้น
15+ pat_eng = re .compile (r'''(?x)
16+ [-a-zA-Z]+| # english
17+ \d[\d,\.]*| # number
3818[ \t]+| # space
3919\r?\n # newline
40- '''
41- pat_eng = re .compile (spat_eng )
42-
43- def multicut (text ,data ):
44- ''' ส่งคืน LatticeString คืนมาเป็นก้อนๆ
45- '''
46- words_at = defaultdict (list ) # main data structure
47- if data != "" : # ถ้าหากกำหนดข้อมูลโดยใช้ dict ของตัวเอง
48- i = 0
49- data2 = copy .copy (data )
50- while i < len (data2 ):
51- data2 [i ]= tcc .tcc (data2 [i ],sep = '#' )
52- if (data2 [len (data2 [i ])- 1 ]!= "#" ):
53- data2 [i ]+= "#"
54- i += 1
55- trie = Trie (data2 )
56- else :
57- trie = file_trie (data = "newmm" )
58- def serialize (p , p2 ): # helper function
59- for w in words_at [p ]:
60- p_ = p + len (w )
61- if p_ == p2 :
62- yield w
63- elif p_ < p2 :
64- for path in serialize (p_ , p2 ):
65- yield w + '/' + path
66- q = {0 }
67- last_p = 0 # last position for yield
68- while min (q ) < len (text ):
69- p = min (q )
70- q -= {p } # q.pop, but for set
20+ ''' )
21+ # TCC
22+ pat_tcc = """\
23+ เc็c
24+ เcctาะ
25+ เccีtยะ
26+ เccีtย(?=[เ-ไก-ฮ]|$)
27+ เccอะ
28+ เcc็c
29+ เcิc์c
30+ เcิtc
31+ เcีtยะ?
32+ เcืtอะ?
33+ เc[ิีุู]tย(?=[เ-ไก-ฮ]|$)
34+ เctา?ะ?
35+ cัtวะ
36+ c[ัื]tc[ุิะ]?
37+ c[ิุู]์
38+ c[ะ-ู]t
39+ c็
40+ ct[ะาำ]?
41+ แc็c
42+ แcc์
43+ แctะ
44+ แcc็c
45+ แccc์
46+ โctะ
47+ [เ-ไ]ct
48+ """ .replace ('c' ,'[ก-ฮ]' ).replace ('t' , '[่-๋]?' ).split ()
7149
72- for w in trie .prefixes (text [p :]):
73- words_at [p ].append (w )
74- q .add (p + len (w ))
50+ def tcc (w ):
51+ p = 0
52+ pat = re .compile ("|" .join (pat_tcc ))
53+ while p < len (w ):
54+ m = pat .match (w [p :])
55+ if m :
56+ n = m .span ()[1 ]
57+ else :
58+ n = 1
59+ yield w [p :p + n ]
60+ p += n
7561
76- if len (q )== 1 :
77- q0 = min (q )
78- yield LatticeString (text [last_p :q0 ], serialize (last_p , q0 ))
79- last_p = q0
62+ def tcc_pos (text ):
63+ p_set = set ()
64+ p = 0
65+ for w in tcc (text ):
66+ p += len (w )
67+ p_set .add (p )
68+ return p_set
69+ def serialize (words_at , p , p2 ):
70+ # find path ทั้งหมด แบบ depth first
71+ for w in words_at [p ]:
72+ p_ = p + len (w )
73+ if p_ == p2 :
74+ yield [w ]
75+ elif p_ < p2 :
76+ for path in serialize (words_at , p_ , p2 ):
77+ yield [w ]+ path
78+ def onecut (text ,data = ['' ]):
79+ if (data != ['' ]):
80+ trie = Trie (data )
81+ else :
82+ trie = Trie (get_data ())
83+ words_at = defaultdict (list ) # main data structure
84+ allow_pos = tcc_pos (text ) # ตำแหน่งที่ตัด ต้องตรงกับ tcc
85+
86+ q = [0 ] # min-heap queue
87+ last_p = 0 # last position for yield
88+ while q [0 ] < len (text ):
89+ p = heappop (q )
8090
81- # กรณี len(q) == 0 คือ ไม่มีใน dict
82- elif len (q )== 0 :
83- m = pat_eng .match (text [p :])
84- if m != None : # อังกฤษ, เลข, ว่าง
85- i = p + m .span ()[1 ]
86- else : # skip น้อยที่สุด ที่เป็นไปได้
87- for i in range (p , len (text )):
88- ww = trie .prefixes (text [i :])
89- m = pat_eng .match (text [i :])
90- if ww or m :
91- break
92- else :
93- i = len (text )
94- w = text [p :i ]
91+ for w in trie .prefixes (text [p :]):
92+ p_ = p + len (w )
93+ if p_ in allow_pos : # เลือกที่สอดคล้อง tcc
9594 words_at [p ].append (w )
96- yield LatticeString (w , in_dict = False )
97- last_p = i
98- q .add (i )
95+ if p_ not in q :
96+ heappush (q , p_ )
9997
100- def mmcut (text ,data = '' ):
101- res = []
102- text = tcc .tcc (text ,sep = '#' ) # ให้นำข้อความมาผ่าน tcc
103- if (text [len (text )- 1 ]!= '#' ): # ถ้าตัวสุดท้ายของสตริงไม่เป็น #
104- text += '#' # ให้เพิ่ม # เข้าไป
105- for w in multicut (text ,data = data ):
106- mm = min (w .multi , key = lambda x : x .count ('/' ))
107- res .extend (mm .split ('/' ))
108- return [x .replace ('#' ,'' ) for x in res if x != '#' ] # เอา # ออก
109- def combine (ww ):
110- if ww == []:
111- yield ""
112- else :
113- w = ww [0 ]
114- for tail in combine (ww [1 :]):
115- if w .unique :
116- yield w + "|" + tail
117- else :
118- for m in w .multi :
119- yield m .replace ("/" ,"|" )+ "|" + tail
98+ # กรณี length 1 คือ ไม่กำกวมแล้ว ส่งผลลัพธ์ก่อนนี้คืนได้
99+ if len (q )== 1 :
100+ paths = serialize (words_at , last_p , q [0 ])
101+ for w in min (paths , key = len ):
102+ yield w
103+ last_p = q [0 ]
120104
121- def listcut (text ,data = '' ):
122- '''
123- ใช้ในการหา list ที่สามารถตัดคำได้ทั้งหมด
124- '''
125- ww = list (multicut (text ,data ))
126- return list (combine (ww ))
127- if __name__ == "__main__" :
128- text = 'ผมรักคุณนะครับโอเคบ่พวกเราเป็นคนไทยรักภาษาไทยภาษาบ้านเกิด'
129- mmcut (text )
130- #print(listcut(text))
105+ # กรณี length 0 คือ ไม่มีใน dict
106+ if len (q )== 0 :
107+ m = pat_eng .match (text [p :])
108+ if m : # อังกฤษ, เลข, ว่าง
109+ i = p + m .end ()
110+ else : # skip น้อยที่สุด ที่เป็นไปได้
111+ for i in range (p + 1 , len (text )):
112+ if i in allow_pos : # ใช้ tcc ด้วย
113+ ww = trie .prefixes (text [i :])
114+ m = pat_eng .match (text [i :])
115+ if ww or m :
116+ break
117+ else :
118+ i = len (text )
119+ w = text [p :i ]
120+ words_at [p ].append (w )
121+ yield w
122+ last_p = i
123+ heappush (q , i )
124+
125+ # ช่วยให้ไม่ต้องพิมพ์ยาวๆ
126+ def mmcut (text ,data = ['' ]):
127+ return list (onecut (text ,data = data ))
0 commit comments