Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

huqie.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. # -*- coding: utf-8 -*-
  2. import copy
  3. import datrie
  4. import math
  5. import os
  6. import re
  7. import string
  8. import sys
  9. from hanziconv import HanziConv
  10. from nltk import word_tokenize
  11. from nltk.stem import PorterStemmer, WordNetLemmatizer
  12. from api.utils.file_utils import get_project_base_directory
  13. class Huqie:
  14. def key_(self, line):
  15. return str(line.lower().encode("utf-8"))[2:-1]
  16. def rkey_(self, line):
  17. return str(("DD" + (line[::-1].lower())).encode("utf-8"))[2:-1]
  18. def loadDict_(self, fnm):
  19. print("[HUQIE]:Build trie", fnm, file=sys.stderr)
  20. try:
  21. of = open(fnm, "r")
  22. while True:
  23. line = of.readline()
  24. if not line:
  25. break
  26. line = re.sub(r"[\r\n]+", "", line)
  27. line = re.split(r"[ \t]", line)
  28. k = self.key_(line[0])
  29. F = int(math.log(float(line[1]) / self.DENOMINATOR) + .5)
  30. if k not in self.trie_ or self.trie_[k][0] < F:
  31. self.trie_[self.key_(line[0])] = (F, line[2])
  32. self.trie_[self.rkey_(line[0])] = 1
  33. self.trie_.save(fnm + ".trie")
  34. of.close()
  35. except Exception as e:
  36. print("[HUQIE]:Faild to build trie, ", fnm, e, file=sys.stderr)
  37. def __init__(self, debug=False):
  38. self.DEBUG = debug
  39. self.DENOMINATOR = 1000000
  40. self.trie_ = datrie.Trie(string.printable)
  41. self.DIR_ = os.path.join(get_project_base_directory(), "rag/res", "huqie")
  42. self.stemmer = PorterStemmer()
  43. self.lemmatizer = WordNetLemmatizer()
  44. self.SPLIT_CHAR = r"([ ,\.<>/?;'\[\]\\`!@#$%^&*\(\)\{\}\|_+=《》,。?、;‘’:“”【】~!¥%……()——-]+|[a-z\.-]+|[0-9,\.-]+)"
  45. try:
  46. self.trie_ = datrie.Trie.load(self.DIR_ + ".txt.trie")
  47. return
  48. except Exception as e:
  49. print("[HUQIE]:Build default trie", file=sys.stderr)
  50. self.trie_ = datrie.Trie(string.printable)
  51. self.loadDict_(self.DIR_ + ".txt")
  52. def loadUserDict(self, fnm):
  53. try:
  54. self.trie_ = datrie.Trie.load(fnm + ".trie")
  55. return
  56. except Exception as e:
  57. self.trie_ = datrie.Trie(string.printable)
  58. self.loadDict_(fnm)
  59. def addUserDict(self, fnm):
  60. self.loadDict_(fnm)
  61. def _strQ2B(self, ustring):
  62. """把字符串全角转半角"""
  63. rstring = ""
  64. for uchar in ustring:
  65. inside_code = ord(uchar)
  66. if inside_code == 0x3000:
  67. inside_code = 0x0020
  68. else:
  69. inside_code -= 0xfee0
  70. if inside_code < 0x0020 or inside_code > 0x7e: # 转完之后不是半角字符返回原来的字符
  71. rstring += uchar
  72. else:
  73. rstring += chr(inside_code)
  74. return rstring
  75. def _tradi2simp(self, line):
  76. return HanziConv.toSimplified(line)
  77. def dfs_(self, chars, s, preTks, tkslist):
  78. MAX_L = 10
  79. res = s
  80. # if s > MAX_L or s>= len(chars):
  81. if s >= len(chars):
  82. tkslist.append(preTks)
  83. return res
  84. # pruning
  85. S = s + 1
  86. if s + 2 <= len(chars):
  87. t1, t2 = "".join(chars[s:s + 1]), "".join(chars[s:s + 2])
  88. if self.trie_.has_keys_with_prefix(self.key_(t1)) and not self.trie_.has_keys_with_prefix(
  89. self.key_(t2)):
  90. S = s + 2
  91. if len(preTks) > 2 and len(
  92. preTks[-1][0]) == 1 and len(preTks[-2][0]) == 1 and len(preTks[-3][0]) == 1:
  93. t1 = preTks[-1][0] + "".join(chars[s:s + 1])
  94. if self.trie_.has_keys_with_prefix(self.key_(t1)):
  95. S = s + 2
  96. ################
  97. for e in range(S, len(chars) + 1):
  98. t = "".join(chars[s:e])
  99. k = self.key_(t)
  100. if e > s + 1 and not self.trie_.has_keys_with_prefix(k):
  101. break
  102. if k in self.trie_:
  103. pretks = copy.deepcopy(preTks)
  104. if k in self.trie_:
  105. pretks.append((t, self.trie_[k]))
  106. else:
  107. pretks.append((t, (-12, '')))
  108. res = max(res, self.dfs_(chars, e, pretks, tkslist))
  109. if res > s:
  110. return res
  111. t = "".join(chars[s:s + 1])
  112. k = self.key_(t)
  113. if k in self.trie_:
  114. preTks.append((t, self.trie_[k]))
  115. else:
  116. preTks.append((t, (-12, '')))
  117. return self.dfs_(chars, s + 1, preTks, tkslist)
  118. def freq(self, tk):
  119. k = self.key_(tk)
  120. if k not in self.trie_:
  121. return 0
  122. return int(math.exp(self.trie_[k][0]) * self.DENOMINATOR + 0.5)
  123. def tag(self, tk):
  124. k = self.key_(tk)
  125. if k not in self.trie_:
  126. return ""
  127. return self.trie_[k][1]
  128. def score_(self, tfts):
  129. B = 30
  130. F, L, tks = 0, 0, []
  131. for tk, (freq, tag) in tfts:
  132. F += freq
  133. L += 0 if len(tk) < 2 else 1
  134. tks.append(tk)
  135. F /= len(tks)
  136. L /= len(tks)
  137. if self.DEBUG:
  138. print("[SC]", tks, len(tks), L, F, B / len(tks) + L + F)
  139. return tks, B / len(tks) + L + F
  140. def sortTks_(self, tkslist):
  141. res = []
  142. for tfts in tkslist:
  143. tks, s = self.score_(tfts)
  144. res.append((tks, s))
  145. return sorted(res, key=lambda x: x[1], reverse=True)
  146. def merge_(self, tks):
  147. patts = [
  148. (r"[ ]+", " "),
  149. (r"([0-9\+\.,%\*=-]) ([0-9\+\.,%\*=-])", r"\1\2"),
  150. ]
  151. # for p,s in patts: tks = re.sub(p, s, tks)
  152. # if split chars is part of token
  153. res = []
  154. tks = re.sub(r"[ ]+", " ", tks).split(" ")
  155. s = 0
  156. while True:
  157. if s >= len(tks):
  158. break
  159. E = s + 1
  160. for e in range(s + 2, min(len(tks) + 2, s + 6)):
  161. tk = "".join(tks[s:e])
  162. if re.search(self.SPLIT_CHAR, tk) and self.freq(tk):
  163. E = e
  164. res.append("".join(tks[s:E]))
  165. s = E
  166. return " ".join(res)
  167. def maxForward_(self, line):
  168. res = []
  169. s = 0
  170. while s < len(line):
  171. e = s + 1
  172. t = line[s:e]
  173. while e < len(line) and self.trie_.has_keys_with_prefix(
  174. self.key_(t)):
  175. e += 1
  176. t = line[s:e]
  177. while e - 1 > s and self.key_(t) not in self.trie_:
  178. e -= 1
  179. t = line[s:e]
  180. if self.key_(t) in self.trie_:
  181. res.append((t, self.trie_[self.key_(t)]))
  182. else:
  183. res.append((t, (0, '')))
  184. s = e
  185. return self.score_(res)
  186. def maxBackward_(self, line):
  187. res = []
  188. s = len(line) - 1
  189. while s >= 0:
  190. e = s + 1
  191. t = line[s:e]
  192. while s > 0 and self.trie_.has_keys_with_prefix(self.rkey_(t)):
  193. s -= 1
  194. t = line[s:e]
  195. while s + 1 < e and self.key_(t) not in self.trie_:
  196. s += 1
  197. t = line[s:e]
  198. if self.key_(t) in self.trie_:
  199. res.append((t, self.trie_[self.key_(t)]))
  200. else:
  201. res.append((t, (0, '')))
  202. s -= 1
  203. return self.score_(res[::-1])
  204. def qie(self, line):
  205. line = self._strQ2B(line).lower()
  206. line = self._tradi2simp(line)
  207. zh_num = len([1 for c in line if is_chinese(c)])
  208. if zh_num < len(line) * 0.2:
  209. return " ".join([self.stemmer.stem(self.lemmatizer.lemmatize(t)) for t in word_tokenize(line)])
  210. arr = re.split(self.SPLIT_CHAR, line)
  211. res = []
  212. for L in arr:
  213. if len(L) < 2 or re.match(
  214. r"[a-z\.-]+$", L) or re.match(r"[0-9\.-]+$", L):
  215. res.append(L)
  216. continue
  217. # print(L)
  218. # use maxforward for the first time
  219. tks, s = self.maxForward_(L)
  220. tks1, s1 = self.maxBackward_(L)
  221. if self.DEBUG:
  222. print("[FW]", tks, s)
  223. print("[BW]", tks1, s1)
  224. diff = [0 for _ in range(max(len(tks1), len(tks)))]
  225. for i in range(min(len(tks1), len(tks))):
  226. if tks[i] != tks1[i]:
  227. diff[i] = 1
  228. if s1 > s:
  229. tks = tks1
  230. i = 0
  231. while i < len(tks):
  232. s = i
  233. while s < len(tks) and diff[s] == 0:
  234. s += 1
  235. if s == len(tks):
  236. res.append(" ".join(tks[i:]))
  237. break
  238. if s > i:
  239. res.append(" ".join(tks[i:s]))
  240. e = s
  241. while e < len(tks) and e - s < 5 and diff[e] == 1:
  242. e += 1
  243. tkslist = []
  244. self.dfs_("".join(tks[s:e + 1]), 0, [], tkslist)
  245. res.append(" ".join(self.sortTks_(tkslist)[0][0]))
  246. i = e + 1
  247. res = " ".join(res)
  248. if self.DEBUG:
  249. print("[TKS]", self.merge_(res))
  250. return self.merge_(res)
  251. def qieqie(self, tks):
  252. tks = tks.split(" ")
  253. zh_num = len([1 for c in tks if c and is_chinese(c[0])])
  254. if zh_num < len(tks) * 0.2:
  255. res = []
  256. for tk in tks:
  257. res.extend(tk.split("/"))
  258. return " ".join(res)
  259. res = []
  260. for tk in tks:
  261. if len(tk) < 3 or re.match(r"[0-9,\.-]+$", tk):
  262. res.append(tk)
  263. continue
  264. tkslist = []
  265. if len(tk) > 10:
  266. tkslist.append(tk)
  267. else:
  268. self.dfs_(tk, 0, [], tkslist)
  269. if len(tkslist) < 2:
  270. res.append(tk)
  271. continue
  272. stk = self.sortTks_(tkslist)[1][0]
  273. if len(stk) == len(tk):
  274. stk = tk
  275. else:
  276. if re.match(r"[a-z\.-]+$", tk):
  277. for t in stk:
  278. if len(t) < 3:
  279. stk = tk
  280. break
  281. else:
  282. stk = " ".join(stk)
  283. else:
  284. stk = " ".join(stk)
  285. res.append(stk)
  286. return " ".join(res)
  287. def is_chinese(s):
  288. if s >= u'\u4e00' and s <= u'\u9fa5':
  289. return True
  290. else:
  291. return False
  292. def is_number(s):
  293. if s >= u'\u0030' and s <= u'\u0039':
  294. return True
  295. else:
  296. return False
  297. def is_alphabet(s):
  298. if (s >= u'\u0041' and s <= u'\u005a') or (
  299. s >= u'\u0061' and s <= u'\u007a'):
  300. return True
  301. else:
  302. return False
  303. def naiveQie(txt):
  304. tks = []
  305. for t in txt.split(" "):
  306. if tks and re.match(r".*[a-zA-Z]$", tks[-1]
  307. ) and re.match(r".*[a-zA-Z]$", t):
  308. tks.append(" ")
  309. tks.append(t)
  310. return tks
  311. hq = Huqie()
  312. qie = hq.qie
  313. qieqie = hq.qieqie
  314. tag = hq.tag
  315. freq = hq.freq
  316. loadUserDict = hq.loadUserDict
  317. addUserDict = hq.addUserDict
  318. tradi2simp = hq._tradi2simp
  319. strQ2B = hq._strQ2B
  320. if __name__ == '__main__':
  321. huqie = Huqie(debug=True)
  322. # huqie.addUserDict("/tmp/tmp.new.tks.dict")
  323. tks = huqie.qie(
  324. "哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈")
  325. print(huqie.qieqie(tks))
  326. tks = huqie.qie(
  327. "公开征求意见稿提出,境外投资者可使用自有人民币或外汇投资。使用外汇投资的,可通过债券持有人在香港人民币业务清算行及香港地区经批准可进入境内银行间外汇市场进行交易的境外人民币业务参加行(以下统称香港结算行)办理外汇资金兑换。香港结算行由此所产生的头寸可到境内银行间外汇市场平盘。使用外汇投资的,在其投资的债券到期或卖出后,原则上应兑换回外汇。")
  328. print(huqie.qieqie(tks))
  329. tks = huqie.qie(
  330. "多校划片就是一个小区对应多个小学初中,让买了学区房的家庭也不确定到底能上哪个学校。目的是通过这种方式为学区房降温,把就近入学落到实处。南京市长江大桥")
  331. print(huqie.qieqie(tks))
  332. tks = huqie.qie(
  333. "实际上当时他们已经将业务中心偏移到安全部门和针对政府企业的部门 Scripts are compiled and cached aaaaaaaaa")
  334. print(huqie.qieqie(tks))
  335. tks = huqie.qie("虽然我不怎么玩")
  336. print(huqie.qieqie(tks))
  337. tks = huqie.qie("蓝月亮如何在外资夹击中生存,那是全宇宙最有意思的")
  338. print(huqie.qieqie(tks))
  339. tks = huqie.qie(
  340. "涡轮增压发动机num最大功率,不像别的共享买车锁电子化的手段,我们接过来是否有意义,黄黄爱美食,不过,今天阿奇要讲到的这家农贸市场,说实话,还真蛮有特色的!不仅环境好,还打出了")
  341. print(huqie.qieqie(tks))
  342. tks = huqie.qie("这周日你去吗?这周日你有空吗?")
  343. print(huqie.qieqie(tks))
  344. tks = huqie.qie("Unity3D开发经验 测试开发工程师 c++双11双11 985 211 ")
  345. print(huqie.qieqie(tks))
  346. tks = huqie.qie(
  347. "数据分析项目经理|数据分析挖掘|数据分析方向|商品数据分析|搜索数据分析 sql python hive tableau Cocos2d-")
  348. print(huqie.qieqie(tks))
  349. if len(sys.argv) < 2:
  350. sys.exit()
  351. huqie.DEBUG = False
  352. huqie.loadUserDict(sys.argv[1])
  353. of = open(sys.argv[2], "r")
  354. while True:
  355. line = of.readline()
  356. if not line:
  357. break
  358. print(huqie.qie(line))
  359. of.close()