diff --git a/code/index_module.py b/code/index_module.py new file mode 100644 index 0000000..4d0bc4e --- /dev/null +++ b/code/index_module.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +""" +Created on Sat Dec 5 23:31:22 2015 + +@author: bitjoy.net +""" + +from os import listdir +import xml.etree.ElementTree as ET +import jieba +import sqlite3 +import configparser + +class Doc: + docid = 0 + date_time = '' + tf = 0 + ld = 0 + def __init__(self, docid, date_time, tf, ld): + self.docid = docid + self.date_time = date_time + self.tf = tf + self.ld = ld + def __repr__(self): + return(str(self.docid) + '\t' + self.date_time + '\t' + str(self.tf) + '\t' + str(self.ld)) + def __str__(self): + return(str(self.docid) + '\t' + self.date_time + '\t' + str(self.tf) + '\t' + str(self.ld)) + +class IndexModule: + stop_words = set() + postings_lists = {} + + config_path = '' + config_encoding = '' + + def __init__(self, config_path, config_encoding): + self.config_path = config_path + self.config_encoding = config_encoding + config = configparser.ConfigParser() + config.read(config_path, config_encoding) + f = open(config['DEFAULT']['stop_words_path'], encoding = config['DEFAULT']['stop_words_encoding']) + words = f.read() + self.stop_words = set(words.split('\n')) + + def is_number(self, s): + try: + float(s) + return True + except ValueError: + return False + + def clean_list(self, seg_list): + cleaned_dict = {} + n = 0 + for i in seg_list: + i = i.strip().lower() + if i != '' and not self.is_number(i) and i not in self.stop_words: + n = n + 1 + if i in cleaned_dict: + cleaned_dict[i] = cleaned_dict[i] + 1 + else: + cleaned_dict[i] = 1 + return n, cleaned_dict + + def write_postings_to_db(self, db_path): + conn = sqlite3.connect(db_path) + c = conn.cursor() + + c.execute('''DROP TABLE IF EXISTS postings''') + c.execute('''CREATE TABLE postings + (term TEXT PRIMARY KEY, df INTEGER, docs TEXT)''') + + for key, value in self.postings_lists.items(): + doc_list = '\n'.join(map(str,value[1])) + t = (key, value[0], doc_list) + c.execute("INSERT INTO postings VALUES (?, ?, ?)", t) + + conn.commit() + conn.close() + + def construct_postings_lists(self): + config = configparser.ConfigParser() + config.read(self.config_path, self.config_encoding) + files = listdir(config['DEFAULT']['doc_dir_path']) + AVG_L = 0 + for i in files: + root = ET.parse(config['DEFAULT']['doc_dir_path'] + i).getroot() + title = root.find('title').text + body = root.find('body').text + docid = int(root.find('id').text) + date_time = root.find('datetime').text + seg_list = jieba.lcut(title + '。' + body, cut_all=False) + + ld, cleaned_dict = self.clean_list(seg_list) + + AVG_L = AVG_L + ld + + for key, value in cleaned_dict.items(): + d = Doc(docid, date_time, value, ld) + if key in self.postings_lists: + self.postings_lists[key][0] = self.postings_lists[key][0] + 1 # df++ + self.postings_lists[key][1].append(d) + else: + self.postings_lists[key] = [1, [d]] # [df, [Doc]] + AVG_L = AVG_L / len(files) + config.set('DEFAULT', 'N', str(len(files))) + config.set('DEFAULT', 'avg_l', str(AVG_L)) + with open(self.config_path, 'w', encoding = self.config_encoding) as configfile: + config.write(configfile) + self.write_postings_to_db(config['DEFAULT']['db_path']) + +if __name__ == "__main__": + im = IndexModule('../config.ini', 'utf-8') + im.construct_postings_lists() diff --git a/code/recommendation_module.py b/code/recommendation_module.py new file mode 100644 index 0000000..fccb112 --- /dev/null +++ b/code/recommendation_module.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 23 14:06:10 2015 + +@author: czl +""" + +from os import listdir +import xml.etree.ElementTree as ET +import jieba +import jieba.analyse +import sqlite3 +import configparser +from datetime import * +import math +import re + +import pandas as pd +import numpy as np + +from sklearn.metrics import pairwise_distances + +class RecommendationModule: + stop_words = set() + k_nearest = [] + + config_path = '' + config_encoding = '' + + doc_dir_path = '' + doc_encoding = '' + stop_words_path = '' + stop_words_encoding = '' + idf_path = '' + db_path = '' + + def __init__(self, config_path, config_encoding): + self.config_path = config_path + self.config_encoding = config_encoding + config = configparser.ConfigParser() + config.read(config_path, config_encoding) + + self.doc_dir_path = config['DEFAULT']['doc_dir_path'] + self.doc_encoding = config['DEFAULT']['doc_encoding'] + self.stop_words_path = config['DEFAULT']['stop_words_path'] + self.stop_words_encoding = config['DEFAULT']['stop_words_encoding'] + self.idf_path = config['DEFAULT']['idf_path'] + self.db_path = config['DEFAULT']['db_path'] + + f = open(self.stop_words_path, encoding = self.stop_words_encoding) + words = f.read() + self.stop_words = set(words.split('\n')) + + def write_k_nearest_matrix_to_db(self): + conn = sqlite3.connect(self.db_path) + c = conn.cursor() + + c.execute('''DROP TABLE IF EXISTS knearest''') + c.execute('''CREATE TABLE knearest + (id INTEGER PRIMARY KEY, first INTEGER, second INTEGER, + third INTEGER, fourth INTEGER, fifth INTEGER)''') + + for docid, doclist in self.k_nearest: + c.execute("INSERT INTO knearest VALUES (?, ?, ?, ?, ?, ?)", tuple([docid] + doclist)) + + conn.commit() + conn.close() + + def is_number(self, s): + try: + float(s) + return True + except ValueError: + return False + + + def construct_dt_matrix(self, files, topK = 200): + jieba.analyse.set_stop_words(self.stop_words_path) + jieba.analyse.set_idf_path(self.idf_path) + M = len(files) + N = 1 + terms = {} + dt = [] + for i in files: + root = ET.parse(self.doc_dir_path + i).getroot() + title = root.find('title').text + body = root.find('body').text + docid = int(root.find('id').text) + tags = jieba.analyse.extract_tags(title + '。' + body, topK=topK, withWeight=True) + #tags = jieba.analyse.extract_tags(title, topK=topK, withWeight=True) + cleaned_dict = {} + for word, tfidf in tags: + word = word.strip().lower() + if word == '' or self.is_number(word): + continue + cleaned_dict[word] = tfidf + if word not in terms: + terms[word] = N + N += 1 + dt.append([docid, cleaned_dict]) + dt_matrix = [[0 for i in range(N)] for j in range(M)] + i =0 + for docid, t_tfidf in dt: + dt_matrix[i][0] = docid + for term, tfidf in t_tfidf.items(): + dt_matrix[i][terms[term]] = tfidf + i += 1 + + dt_matrix = pd.DataFrame(dt_matrix) + dt_matrix.index = dt_matrix[0] + print('dt_matrix shape:(%d %d)'%(dt_matrix.shape)) + return dt_matrix + + def construct_k_nearest_matrix(self, dt_matrix, k): + tmp = np.array(1 - pairwise_distances(dt_matrix[dt_matrix.columns[1:]], metric = "cosine")) + similarity_matrix = pd.DataFrame(tmp, index = dt_matrix.index.tolist(), columns = dt_matrix.index.tolist()) + for i in similarity_matrix.index: + tmp = [int(i),[]] + j = 0 + while j < k: + max_col = similarity_matrix.loc[i].idxmax(axis = 1) + similarity_matrix.loc[i][max_col] = -1 + if max_col != i: + tmp[1].append(int(max_col)) #max column name + j += 1 + self.k_nearest.append(tmp) + + def gen_idf_file(self): + files = listdir(self.doc_dir_path) + n = float(len(files)) + idf = {} + for i in files: + root = ET.parse(self.doc_dir_path + i).getroot() + title = root.find('title').text + body = root.find('body').text + seg_list = jieba.lcut(title + '。' + body, cut_all=False) + seg_list = set(seg_list) - self.stop_words + for word in seg_list: + word = word.strip().lower() + if word == '' or self.is_number(word): + continue + if word not in idf: + idf[word] = 1 + else: + idf[word] = idf[word] + 1 + idf_file = open(self.idf_path, 'w', encoding = 'utf-8') + for word, df in idf.items(): + idf_file.write('%s %.9f\n'%(word, math.log(n / df))) + idf_file.close() + + def natural_sort(self, l): + convert = lambda text: int(text) if text.isdigit() else text.lower() + alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] + return sorted(l, key = alphanum_key) + + def partition_files(self, n_partitions): + files = listdir(self.doc_dir_path) + files = self.natural_sort(files) + n = float(len(files)) + partitions = [] + sz = int(n / n_partitions) + x = 0 + for i in range(n_partitions - 1): + partitions.append(files[x: x + sz]) + x += sz + partitions.append(files[x:]) + return partitions + + def find_k_nearest(self, k, topK, n_partitions): + + print('-----start gen_idf_file time: %s-----'%(datetime.today())) + self.gen_idf_file() + + partitions = self.partition_files(n_partitions) + i = 1 + for p in partitions: + print('-----start %d construct_dt_matrix time: %s-----'%(i, datetime.today())) + dt_matrix = self.construct_dt_matrix(p, topK) + + print('-----start %d construct_k_nearest_matrix time: %s-----'%(i, datetime.today())) + self.construct_k_nearest_matrix(dt_matrix, k) + i += 1 + + print('-----start write_k_nearest_matrix_to_db time: %s-----'%(datetime.today())) + self.write_k_nearest_matrix_to_db() + +if __name__ == "__main__": + print('-----start time: %s-----'%(datetime.today())) + rm = RecommendationModule('../config.ini', 'utf-8') + rm.find_k_nearest(5, 25, 10) + print('-----finish time: %s-----'%(datetime.today())) + \ No newline at end of file diff --git a/code/setup.py b/code/setup.py new file mode 100644 index 0000000..5a3b1d9 --- /dev/null +++ b/code/setup.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Dec 25 00:04:40 2015 + +@author: czl +""" + +from index_module import IndexModule +from recommendation_module import RecommendationModule +from datetime import * + + +print('-----start indexing time: %s-----'%(datetime.today())) +im = IndexModule('../config.ini', 'utf-8') +im.construct_postings_lists() +print('-----start recommending time: %s-----'%(datetime.today())) +rm = RecommendationModule('../config.ini', 'utf-8') +rm.find_k_nearest(5, 25, 10) +print('-----finish time: %s-----'%(datetime.today())) \ No newline at end of file diff --git a/config.ini b/config.ini index 6b9564a..becf69d 100644 --- a/config.ini +++ b/config.ini @@ -7,6 +7,6 @@ idf_path = ../data/idf.txt db_path = ../data/ir.db k1 = 1.5 b = 0.75 -n = 102416 -avg_l = 348.61414232151225 +n = 1000 +avg_l = 462.781 diff --git a/data/ir.db b/data/ir.db new file mode 100644 index 0000000..698ed61 Binary files /dev/null and b/data/ir.db differ diff --git a/data/stop_words.txt b/data/stop_words.txt new file mode 100644 index 0000000..5d3bcc9 --- /dev/null +++ b/data/stop_words.txt @@ -0,0 +1,1341 @@ +知道 +′| +失去 +[②] +]∧′=[ +咳 +迅速 +那儿 +一次 +不问 +如同 +共同 +有力 +重新 +似乎 +1. +各级 +抑或 +起 +=- +=( +[①C] +不是 +曾经 +[②e] +它们 +[- +不变 +唯有 +不仅 +为止 +类如 +固然 +了解 +不至于 +》), +密切 +这样 +前者 +虽然 +》 +〔 +[②j] +除非 +两者 +怎么办 +多数 +就是 +截至 +尔尔 +以后 +→ +可是 +另外 +一直 +使用 +眨眼 +然后 +遵循 +只是 +以外 +例如 +γ +咚 +特点 +, +经过 +因此 +5 +所以 +只怕 +[] +而言 +~+ +俺们 +[④d] +总而言之 +比方 +她的 +一时 +还是 +加强 +反之 +换句话说 +多次 +? +哦 +A +咱们 +哎 +论 +哼唷 +至若 +# +才 +[②①] +使 +今後 +起来 +有着 +仍 +孰知 +此间 +反应 +一天 +方便 += +其 +这么点儿 +做到 +一旦 +任凭 +这里 +逐步 +最 +还有 +吓 +反过来 +不够 +毫不 +突然 +他人 +云云 +处理 +以故 +借傥然 +后面 +[③g] +去 +具体地说 +孰料 +遇到 +首先 +越是 +[②g] +相对而言 +要不然 +而已 +确定 +开始 +尚且 +哪样 +正值 +到 +出于 +[②②] +纵然 +下列 +哼 +造成 +就 +坚持 +不惟 +再者说 +之前 +与此同时 +犹且 +[③b] +为此 +自打 +[①D] +1 +乘 +自后 +普遍 +全体 +一 +维持 +这个 +任何 +℃ +}> +② +尤其 +况且 +假如 +代替 +让 +故 +自己 +哗 +来着 +哈 +φ +尽管 +等 +先不先 +〕〔 +一些 +-[*]- +突出 +嘻 +她 +⑤ +即若 +2 +转动 +不断 +得了 +一起 +扩大 +经 +那些 +其中 +中 +取得 +必要 +’ +运用 +再 +通常 +兼之 +比及 +一边 +7 +[①⑨] +冲 +注意 +立即 +内 +再者 +随 +限制 +准备 +叫做 +彻底 +便于 +哩 +乃至 +喏 +][ +~ +赶 +不若 +又及 +替代 +中小 +先后 +来自 +其次 +上下 +遵照 +而且 +。。。。。。 +甚或 +................... +e] +难道说 +管 +以致 +自家 +Ⅲ +莫不然 +趁 +这般 +有的 +… +[②h] +俺 +允许 +任 +严重 +归齐 +成为 +哪年 +哈哈 +却不 +们 +上来 +[⑨] +何况 +8 +嗡嗡 +乃 +别的 +比 +) +可 +啦 +看来 +照着 +庶几 +如 +像 +∈[ +个别 +只限 +往往 +伟大 +既 +人家 +至今 +[①E] +适用 +实现 +于是 +除此之外 +者 +这麽 +[② +简言之 +A +-β +, +惟其 +除开 +着 +[①d] +那边 +[ +一则 +那般 +余外 +罢了 +大大 +В ++ξ +能够 +些 +纵 +按照 +每年 +这点 +exp +矣哉 +从此 +譬如 +6 +啷当 +〉 +[①④] +上去 +不外乎 +Lex +要 +说说 +考虑 +总是 +并且 +鉴于 +构成 +各 +别管 +而外 +且说 +...... +>λ +[⑤d] +凡 +巴巴 +呜呼 +避免 +吱 +那么些 +:// +[⑤b] +[⑧] +顺 +_ +②c +] +▲ +或者 +主要 +怎样 +.. +— +各地 +前后 +不管 +××× +大多数 +呗 +各位 +目前 +倘然 +8 +达到 +变成 +嗳 +设若 +有时 +不会 +所在 +’‘ +7 +ng昉 +[①] +所谓 +有些 +本人 +3 +几乎 +自身 +最近 +之类 +逐渐 +<φ +得 +基本 +认识 +[①i] +宣布 +属于 +那么 +” +至于 +始而 +您 +大家 +怎奈 +然而 +前进 +这么 +他们们 +谁人 +矣乎 +不同 +总的来说 +叮咚 +此 +兮 +具体说来 +形成 +[①c] +[④a] +整个 +不只 +[①⑤] +每个 +当着 +或 +甚至于 +这么样 +)、 +[①f] +与否 +<Δ +好 +吧哒 +需要 +又 +sub +@ +觉得 +有著 +的 +[①③] +距 +任务 +尽管如此 +使得 +纵令 +9 +后者 +· +真正 +出来 +无 +换言之 +什么样 +? +」 +于是乎 +由 +按 +。。。。。 +附近 +不能 +一片 +同时 +既然 +. +之後 +以免 +意思 +有所 +上升 +: +\ +如何 +——— +根本 +[*] +凡是 +:: +等到 +哉 +『 +你的 +正在 +看出 +① +依照 +9 +加之 +此处 +那样 +上 +啥 +互相 +正巧 +上面 +] +而后 +毋宁 +倘若 +哎哟 +[①⑦] +何时 +[②⑤] +纵使 +μ +/ +庶乎 +望 +一定 +一方面 +{- +假使 +得出 +φ. +的话 +的确 +前此 +由于 +已 +因为 +哟 +进行 +是的 +[①B] +[①①] +据 +这时 +乌乎 +[②⑥] +[③c] +把 +没有 +若 +拿 +喽 +打从 +[②c] +那么样 +这边 +由此可见 +后来 +果真 +保持 +她们 +强烈 +还要 +但凡 +" +特别是 +[①⑥] +嘎登 +要么 +并不是 +了 +已矣 +不要 +遭到 +0:2 +对待 +下面 +各人 +倘使 +复杂 +当地 +相应 +再则 +里面 +有关 +必须 +不妨 +为着 +对应 +处在 +>> +反而 +规定 +重大 +[⑤e] +乃至于 +[③⑩] +儿 +( +<λ +即使 +仍旧 +显然 +呼哧 +` +b] +或曰 +除了 +据此 +及其 +看看 +各自 +哪些 +向着 +为何 +一下 +咋 +不一 +绝对 +同一 +在下 +[③F] +︿ +[③d] +[②i] +高兴 +最好 +呕 +关于具体地说 +{ +完成 +第 ++ +< +嘘 +都 +就算 +进而 +时候 +焉 +跟 +[②③] +而 +帮助 +起见 +给 +或则 +有效 +莫若 +不 +别处 +嘿 +咦 +大批 +广大 +[③e] +如果 +引起 +不久 +受到 +不特 +呜 +此地 +③ +只当 +万一 +以 +呢 +即 +上述 +-- +小 +正常 +即令 +说来 +> +我们 +什么 +sup +欢迎 +[⑤a] +自 +甚么 +为什么 +直接 +显著 +[④e] +开外 +积极 +左右 +左 +右 +依靠 +尽 +咧 +无法 +问题 +* +曾 +待 +不然 +继之 +一致 +然後 +开展 +甚至 +但是 +如上所述 +被 +诚然 +呵呵 +<< +依 +诸如 +这些 +哪 +其实 +[③a] +── +来说 +促进 +进步 +人们 +比较 +靠 +不成 +⑩ +& +彼此 +[④c] +他的 +作为 +另一方面 +倘或 +相似 +... +产生 +哪个 +++ +极了 +即或 +大力 +後面 +何以 +争取 +合理 +嘛 +某些 +和 +则 +一样 +战斗 +将 +中间 +彼 +' +≈ +另 +啪达 +认为 +诸位 +非徒 +㈧ +继后 +具有 +⑨ +那 +行动 +[④b] +- +总结 +可以 +譬喻 +贼死 +之一 +亦 +它 +不得 +怎 +[①②] +怎么 +周围 +几 +设或 +也是 +现代 +之所以 +认真 +以至于 +光是 +~ +不但 +省得 +已经 +没奈何 +是不是 +你们 +多少 +何处 +谁知 +宁 +LI +不足 +他 +不尽 +多么 +哇 +看见 +巴 +0 +几时 +看到 +[⑩] +( +自从 +非特 +[③h] +用 +某某 +[②a] +不单 +既往 +临 +及 +然则 +由此 +转变 +f] +宁肯 +决定 +。。。。 +. +¥ +倘 +随时 +之 +虽 +={ +下来 +宁可 +先生 +向使 +[②b] +; +可能 +一何 +连 +采取 +呃 +练习 +如是 +‘ +较 +综上所述 +那麽 +喂 +吧 +出去 +随著 +特殊 +不光 +} +丰富 +竟而 +结合 +部分 +趁着 +只 +企图 +相反 +这种 += +着呢 +大 +咱 +③] +此次 +当 +适应 +漫说 +[ +再其次 +很 +才能 +你 +)÷(1- +当然 +于 +嗯 +是以 +再说 +么 +宁愿 +致 +顺着 +何 +吗 +若非 +且 +[②⑧] +旁人 +每当 +欤 +冒 +好的 +c] +_ +这会儿 +〕 +替 +! +4 +过去 +今后 +不如 +本 +非独 +[②B] +归 +朝着 +嗡 +[①⑧] +故而 +甚而 +其一 +如若 +今天 +由是 +也好 +常常 +掌握 +普通 +' +[⑤]] +喔唷 +总之 +〈 +要求 +及时 +许多 +就是说 +对 +今 +与 +全面 +比如 +召开 +[②G] +它的 +好象 +较之 +也罢 +沿 +其二 +组成 +实际 +应当 +④ +a] +所 +对方 +某 +[①h] +因了 +从而 +[②⑩] +莫如 +@ +即便 +且不说 +呸 +而况 +别说 +所幸 +能 +明显 +! +专门 +真是 +[①g] +最大 +方面 +除 +什麽 +存在 +=[ +一般 +己 +满足 +等等 +多 +因 +沿着 +[⑥] +能否 +只有 +凭借 +若夫 +移动 +接着 +并非 +假若 +阿 +2.3% +~~~~ +反映 +原来 +来 +/ +—— +[⑦] +是 +[①o] +介于 +不怕 +犹自 +5:0 +呵 +向 +地 +其他 +根据 +正如 +直到 +{ +设使 +// +从 +嘎 +$ +举行 +$ +Δ +相信 +并不 +谁 +哎呀 +别 +R.L. +以便 +依据 +这儿 +不尽然 +此外 +啐 +不敢 +该 +哪儿 +紧接着 +叫 +> +岂但 +也 +通过 +嘿嘿 +加以 +2 +对于 +,也 +同 +这 +乎 +前面 +′∈ +非常 +般的 +却 +具体 +“ +适当 +随后 +呀 +- +[⑤f] +容易 +啊 +完全 +本身 +[③] +一面 +全部 +一切 +1 +尔 +诚如 +6 +有及 +5 +行为 +继而 +《 +说明 +结果 +防止 +[①e] +其余 +要不是 +这就是说 +无宁 +| +正是 +总的来看 +再有 +看 +并没有 +用来 +后 +基于 +』 +12% +■ +怎么样 +与其 +别人 +尔后 +虽说 +如其 +大约 +[②④ +从事 +[④] +并 +至 +”, +本着 +有 +双方 +得到 +以前 +* +另悉 +以至 +广泛 +唉 +Ψ +那会儿 +当时 +进入 +重要 +| +哪边 +云尔 +# +在 +获得 +先後 +可见 +朝 +无论 +以为 +…… +最後 +非但 +各种 +那里 +: +因而 +分别 +其它 +加入 +【 +为主 +同样 +以及 +最高 +就是了 +除外 +现在 +不料 +如下 +】 +总的说来 +严格 +0 +出现 +则甚 +少数 +以下 +别是 +如此 +所有 +安全 +< +范围 +後来 +我 +甚且 +谁料 +[②f] +为 +在于 +自各儿 +为了 +自个儿 +过 +愿意 +若果 +为什麽 +每天 +[②d] +哪天 +充分 +离 +=☆ +个人 +反过来说 +恰恰相反 +。。 +以後 +边 +照 +明确 +只要 +似的 +不比 +而是 +下去 +; +⑥ +彼时 +他们 +这么些 +有利 +主张 +怎麽 +人 +。 +[⑤] +× +与其说 +巨大 +深入 +嗬 +这次 +) +不拘 +就要 +二来 +或是 +慢说 +那时 +仍然 +腾 +还 +以来 +} +[①a] +一转眼 +不过 +清楚 +要是 +巩固 +对比 +矣 +应用 +% +它们的 +随着 +年 +月 +日 +今年 +诸 +针对 +这一来 +3 +、 +…………………………………………………③ +此时 +是否 +只消 +不可 +转贴 +表示 +十分 ++ +否则 +但 +大量 +及至 +某个 +⑧ +-- +接著 +更加 +打 +联系 +故此 +果然 +[①A] +连同 +以上 +坚决 +心里 +鄙人 +下 +强调 +<± +继续 +个 +因着 +我的 +关于 +。。。 +以期 +过来 +∪φ∈ +赖以 +相同 +要不 +之后 +必然 +那个 +哪怕 +4 +哪里 +最后 +有点 +各个 +经常 +即如 +不独 +集中 +当前 +ZXFITL +相等 +~± +应该 +相对 +=″ +既是 +[③①] +表明 +⑦ +& +良好 +若是 +本地 +一来 +凭 +[②⑦] +% +不论 +如上 +↑ +往 +^ +相当 +每 +虽则 \ No newline at end of file