利用古诗词做填字游戏是一项很有趣的活动,通常的填字游戏都是由几横几竖构成,如下图:
显然,横竖交叉的位置就是两句诗共有的字。那么,问题来了,如何从众多诗文中找到有共同字的句子呢?
这里Mr. PosPro用Python写了一个小程序,可以生成简单填字游戏(的模型),程序输出的效果如下:
可以看到,程序从全唐诗中找到了3句有共同字的句子,并以合适的位置完成了排列。?和#代表了交叉处的字,同时在屏幕下方给出了最后答案。
下面Mr. PosPro教你如何实现这个程序。程序总体上分为三个部分:
(一)从TXT文件《全唐诗》中提取有用信息,并按照我们需要的格式保存到新文件中
(二)实现一个在DOS窗口的输出程序,以便在指定位置输出特定文字
(三)核心部分,抽取诗句,找到关联的字,确定每一个字的输出位置,并把最后结果交给(二)中实现的程序
本次主要讨论第(一)部分内容,其它部分的实现请参见后续博客。
想要做一个古诗词的填字游戏,首先得收集到足够多的诗句作为原料库。可以在网上搜索“全唐诗TXT“,我选择的版本大概有8.2M,内容如图:
这个版本适合直接阅读,但却不适合用程序处理,所以首先得写一段程序,把这四百多万字的文本文件,转化成我想要形式。对此我是这样设计的:
1>去掉所有无关信息,只保留标题,作者,诗文内容(标点符号也不要)
2>一首诗的所有信息都在一行中表达,从左到右依次为:行号,题目,作者,诗句全文,所有内容Tab隔开。
即,形成如下这个样子
下面就是具体的程序实现了:
1.读入文件
i=3200 # PosPro says:在测试时无需读取全部信息,可以通过此参数调整读入行数,加快测试
with open('全唐诗.txt',encoding='gbk',errors="ignore") as f:
for line in f:
line=line.rstrip().lstrip() #去除左右空白字符
if i>0:
analyzeText(line)
i-=1
else:
break
代码很简单,但有个小技巧可以和大家分享一下:由于文件很大(超过400万字),在测试阶段如果一次性读入的话,会很耗时间。这里用i控制一下读入的行数。毕竟,我们首先要验证的是功能的正确。
2. analyzeText函数在干什么?
仔细分析《全唐诗》的文本,可以发现一个特点,即‘卷’和‘【’同时出现的那一行就是诗文的起始,我们应该以此为标志,将程序分为寻找下一首诗,处理标题,处理诗文等几个阶段,代码如下:(PosPro says: Python的优美之处就在于,程序本身和对程序的解释几乎是一体的,你读懂了代码也就理解了代码。当然,我也会加上足够多的注释的。)
INDEXNUM=0
EMPTYLINE=0
STATEFLAG=0
def analyzeText(line):
global INDEXNUM, EMPTYLINE, STATEFLAG
if line=='':
EMPTYLINE+=1
#PosPro says:构成一个无限循环,只有通过return才能够退出整个函数,读取下一行
while (True):
if STATEFLAG==0:
#0:始状态,在此状态下若发现某一行同时包含'卷'和'【',则进入诗句标题
if ('卷' in line) and ('【' in line):
STATEFLAG=1
else:
return
#1: 表示当前句为标题
if STATEFLAG==1:
INDEXNUM+=1
processTitle(line)
STATEFLAG=2
EMPTYLINE=0
return
#2: 表示正在读取诗文,但需要特别考虑空行和进入下一首诗标题的情况
if STATEFLAG==2:
if EMPTYLINE>2:
processEndPoem()
STATEFLAG=0
EMPTYLINE=0
return
elif ('卷' in line) and ('【' in line):
processEndPoem()
STATEFLAG=1
EMPTYLINE=0
#PosPro says:此处不return,因为该line还需交由状态1处理
else:
processPoemText(line)
return
3. 分而治之,实现对标题、诗文,以及结束的分别处理。三个函数一起给出:
def processTitle(line):
print (str(INDEXNUM), end='\t') #INDEX就是我自己做的诗文索引
idx1=line.find('【')
idx2=line.find('】')
poemTitle=line[idx1+1:idx2]
author=line[idx2+1:]
print(poemTitle,end='\t')
if author.rstrip()=='':
print ('佚名',end='\t') #发现有些诗句没有注明作者,那我就自己标一下
else:
print(author,end='\t')
def processPoemText(line):
#此时已深入到诗句中了,要将各种标点符号删掉,并将每句诗文作为list中的一项
if not line=='':
#如果要以多个不同字符作为分隔符,就必须用
everyLine=re.split(',|。',line)
for l in everyLine:
print (l, end='\t')
def processEndPoem():
print ('') #完成一个换行
4. 等等,不是说要产生一个新文件么?怎么都是在DOS窗口显示的啊?
别急,这就是Python另一个优雅之处了。在命令行敲完文件名之后,加上">1.txt",想输出到哪里就到哪里
附:全部代码如下:
## Created by PosPro
## http://blog.csdn.net/pospro
import re
i=3200 # PosPro says:在测试时无需读取全部信息,可以通过此参数调整读入行数,加快测试
INDEXNUM=0
EMPTYLINE=0
STATEFLAG=0
def processTitle(line):
print (str(INDEXNUM), end='\t') #INDEX就是我自己做的诗文索引
idx1=line.find('【')
idx2=line.find('】')
poemTitle=line[idx1+1:idx2]
author=line[idx2+1:]
print(poemTitle,end='\t')
if author.rstrip()=='':
print ('佚名',end='\t') #发现有些诗句没有注明作者,那我就自己标一下
else:
print(author,end='\t')
def processPoemText(line):
#此时已深入到诗句中了,要将各种标点符号删掉,并将每句诗文作为list中的一项
if not line=='':
#PosPro says:如果要以多个不同字符作为分隔符,就必须用到re模块了
everyLine=re.split(',|。',line)
for l in everyLine:
print (l, end='\t')
def processEndPoem():
print ('') #完成一个换行
def analyzeText(line):
global INDEXNUM, EMPTYLINE, STATEFLAG
if line=='':
EMPTYLINE+=1
#PosPro says:构成一个无限循环,只有通过return才能够退出整个函数,读取下一行
while (True):
if STATEFLAG==0:
#0:始状态,在此状态下若发现某一行同时包含'卷'和'【',则进入诗句标题
if ('卷' in line) and ('【' in line):
STATEFLAG=1
else:
return
#1: 表示当前句为标题
if STATEFLAG==1:
INDEXNUM+=1
processTitle(line)
STATEFLAG=2
EMPTYLINE=0
return
#2: 表示正在读取诗文,但需要特别考虑空行和进入下一首诗标题的情况
if STATEFLAG==2:
if EMPTYLINE>2:
processEndPoem()
STATEFLAG=0
EMPTYLINE=0
return
elif ('卷' in line) and ('【' in line):
processEndPoem()
STATEFLAG=1
EMPTYLINE=0
#PosPro says:此处不return,因为该line还需交由状态1处理
else:
processPoemText(line)
return
with open('全唐诗.txt',encoding='gbk',errors="ignore") as f:
for line in f:
#去除左右空白字符
line=line.rstrip().lstrip()
if i>0:
analyzeText(line)
i-=1
else:
break