利用古詩詞做填字遊戲是一項很有趣的活動,通常的填字遊戲都是由幾橫幾豎構成,如下圖:
顯然,橫豎交叉的位置就是兩句詩共有的字。那麼,問題來了,如何從衆多詩文中找到有共同字的句子呢?
這裏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