
球球各位大佬不要在评论区里霸凌我了
我已经知道开那么多线程是个很弱智的行为了呜呜呜呜
最近QQ推出了一个成语接龙红包的功能,然后有些人发现了一个顶俩这个可怕的词。看了 @张逸群 的文章后,我觉得我大概也能写出一个类似的小东西出来。
首先,我做了点资料搜集,发现清华大学有一个开放的中文词语库,果断下载下来。
下载完后是一个txt文件,里面每行是一个成语。可以看到词库里有不是四字的成语和一些我们不需要的数字,所以要在程序里去除掉不需要的数据。

随后,我把所有词按照首和尾的拼音分类了一下,这部分的代码如下:
heads = {}
tails = {}
# Parse the source
with open('THUOCL_chengyu.txt', 'r', encoding='utf-8') as source:
idioms = [i for i in map(lambda x: x.split()[0], source.readlines()) if len(i) <= 4]
# Filter by heads and tails
for idiom in idioms:
head = getHead(idiom)
tail = getTail(idiom)
# 按首拼音分类
if head not in heads:
heads[head] = [idiom]
else:
heads[head].append(idiom)
# 按尾拼音分类
if tail not in tails:
tails[tail] = [idiom]
else:
tails[tail].append(idiom)
这样,就可以开始生成接龙啦
首先,我设置接龙目标为‘一个顶俩’,一个顶俩的首拼音为‘yi’,于是找尾为‘yi’的成语,然后从那些成语继续往下爬,爬到一定层数的时候停止。这样的话每爬一次就是一个接龙,把爬的顺序存到输出文件里面。
如果用很无聊很无聊的技术语言来说的话:就是从根开始深度优先遍历一颗树,每遍历一个节点就把遍历的路径存到结果列表里面。
于是,现在的结果:

显然...这种单线程遍历八千多个节点互相链接的树是很慢的...于是改成多线程吧!
开20000个线程跑起来!
(ps:没贴源代码是因为修改后这段时间的代码没备份)

终于,跑了差不多一个半小时后,程序跑完了。
看看结果吧!
哇,整整两亿多条,刺激嗷
等一下下...结果好像有点不对?

emmm....貌似有点问题,难不成我一个半小时白爬了???
然后我经过我的仔细观察后发现...

顺 序 反 了

emm...这个还是可以挽救的...于是我写了个脚本来修改结果的顺序。处理比较简单:除了最后一个成语之外其他顺序倒过来
以下是代码:
# 加一个winsound后在处理完可以有提示音
import winsound
# 读取
with open('results.txt', 'r', encoding='utf-8') as source:
lines = source.readlines()
winsound.Beep(400, 300)
print('Reading done')
# 处理
result = map(lambda x: '->'.join(x.strip().split('->')[0:-1][::-1]) + '->一个顶俩', lines)
winsound.Beep(450, 300)
print('Processing done')
# 输出
with open('results.txt', 'w', encoding='utf-8') as output:
output.write('n'.join(result))
winsound.Beep(500, 300)
print('Finished!')
这部分生成的结果在文章最结尾会打包发出来(大小差不多1.5g)
然后,作为一个(垃圾)程序员,肯定不会满意一个半小时的输出时间,所以我开始了一去不复返的优化之路。
至于怎么优化的我就不详述了(反正没人想看 我也不知道),总的来说,改成MySQL存储结果,减少需要遍历的节点blablabla
直接贴无聊的代码:
(是的我忘做注释了请各位程序员上帝原谅我呜呜呜)
# Put in initial jobs
jobs = Queue()
if TARGET in tails:
for idiom in tails[TARGET]: jobs.put((idiom, [TARGET_IDIOM], 1))
def worker():
db = MySQLdb.connect('localhost', 'root', 'lq369258147123')
db.set_character_set('utf8')
c = db.cursor()
c.execute('USE idioms')
while jobs.not_empty:
idiom, path, depth = jobs.get()
head = getHead(idiom)
c.execute('''INSERT INTO `connections`
(`Initial`, `Head`, `Path`, `Depth`)
VALUES
("{}", "{}", "{}", {})'''.format(head, idiom, '->'.join([idiom] + path), depth)
)
db.commit()
jobs.task_done()
if depth == MAX_DEPTH - 1 and head in tails:
c.execute('''INSERT INTO `connections`
(`Initial`, `Head`, `Path`, `Depth`)
VALUES
{}'''.format(','.join(['("{}", "{}", "{}", {})'.format(getHead(i), i, '->'.join([i, idiom] + path), MAX_DEPTH) for i in tails[head]]))
)
db.commit()
elif depth < MAX_DEPTH and head in tails:
for i in tails[head]:
jobs.put((i, [idiom] + path, depth + 1))
c.close()
db.close()
这时有个朋友告诉我还有个接不了的成语叫‘救过不给’
换目标成语,5000个线程跑起来!(2w个线程同时链接数据库会崩掉)


终于在差不多十五分钟后,生成完了。
生成了差不多四百万个接龙(比一个顶俩少很多)



当然...作为一个后端,怎么会只满足于一个数据库呢,肯定要写一个接口和前端呀。
说做就做,走起!
先写了个简单的api:

然后写了一个非常少女心(?)的前端:

然后不知为什么脑子一抽想用docker和nginx搞一个比较有逼格的结构,但是一个都不会用...
但是作为一个(垃圾)程序员,有什么不会的,上谷歌学咯
于是花了一天把docker和nginx入门了!
接下来就是部署啦!
花了二十块钱买了个腾讯云的学生服务器,部署了上去。
如果你想要用的话链接在这里:
成语接龙工具49.234.224.123(为什么没域名?因为我没钱+懒得备案)
折腾了这么久,终于把这个小工具做完了。
是时候好好在我朋友们发接龙红包的时候好好整整他们啦!
欸...我好像没朋友
rip
下载链接:https://pan.baidu.com/s/115F2-JYT-Udbdmok0foncQ
提取码:hfdu
里面有一个顶俩的txt文件和救过不给的sql文件
总结:
总体来说做这个小工具还是满有价值的,首先是半个暑假都没写代码手都生了,写这个小工具复习一下。其次就是终于学会了docker和nginx,一下完成了今年给自己定的两个小目标。
还有就是,我知道这不是什么真的大数据项目,而且我在文中做的东西可能都很弱智,但我只是个学生,所以请各位真正的程序员大佬体谅一下....
最后 谢谢你们观看 祝各位读者们 身体健康
uwu