Python3程序设计实验四
文章摘自:Python3程序设计实验|PengWill
面向对象日期类
程序设计说明
设计一个日期类,实现和日期有关的操作。如计算两个日期之间的间隔,指定日期后若干天对应的日期,比较两个日期的大小等。可自行拓展其他功能,如判断一个日期是一年的第几天,改日期是星期几,是一年中的第几周,按指定格式打印日期,求该日期对应的农历日期等等。
程序设计思路
首先类中要有判断是否合法的功能。对于月份考虑到用户可能输入英文的简写,故在内部应该建立月份英文简写和全拼的字典。在读入用户输入后,全部转换成大写来进行,在字典进行匹配。若包含非法输入,则给出用户提示。
其余的功能执行之前应该首先检查对象的输入是否合法,再执行相关操作。用户在手动修改了年月日,应该首先调用isLeagal()方法检查输入是否合法,或者直接调用其他方法,其他方法会首先调用isLeagal()方法检查合法性。
日期类的功能判断是否为闰年,一年中的第几天,一年中的第几周,日期对应的星期,计算两个日期的日期差,当前日期之后几天的日期是多少,打印月历,打印年历,和按照一定格式输出日期等。
程序代码
Date.py
class Date():
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
self.leagal = True
self.leap = False
self.MonthDict = {'JAN':1,'FEB':2,'MAR':3,'APR':4,'MAY':5,'JUN':6,'JUL':7, \
'AUG':8, 'SEPT':9, 'OCT':10, 'NOV':11, 'DEC':12, 'JANUARY':1,\
'FEBRUARY':2, 'MARCH':3, 'APRIL':4, 'MAY':5,'JUNE':6,'JULY':7,\
'AUGUST':8, 'SEPTEMBER':9, 'OCTOBER':10, 'NOVEMBER':11, 'DECEMBER':12
}
self.HashList = [0]
times = 0
for k,v in self.MonthDict.items():
self.HashList.append(k)
if times == 12:
break
else:
times +=1
self.DayList = [0,31,28,31,30,31,30,31,31,30,31,30,31]
self.isLeagal()
self.GiveTips()
def isLeagal(self):
# 判断输入的日期是否合法
if isinstance(self.year,int) == False:
self.leagal = False
else:
self.isLeap()
if self.leap:
self.DayList[2] = 29
self.TOT = 366
else:
self.DayList[2] = 28
self.TOT = 365
if isinstance(self.month,int) == True:
if self.month >= 1 and self.month <=12:
pass
else:
self.leagal = False
elif isinstance(self.month,str) == True:
self.month = self.month.upper()
if self.month in self.MonthDict:
self.month = self.MonthDict[self.month]
else:
self.leagal = False
if isinstance(self.day,int) == True:
if self.day >=1 and self.day <= self.DayList[self.month]:
pass
else:
self.leagal = False
else:
self.leagal = False
return self.leagal
def isLeap(self):
# 判断输入的是否为闰年
if (self.year % 100 != 0 and self.year % 4 == 0) or (self.year % 400 == 0):
self.leap = True
else:
self.leap = False
return self.leap
def GiveTips(self):
# 若日期非法,则给出提示
if not self.isLeagal():
print('日期格式非法')
def DayOfYear(self):
# 计算当前日期是一年的第几天
if not self.isLeagal():
self.GiveTips()
return
num = self.month
tot = 0
for i in range(0,num):
tot += self.DayList[i]
tot += self.day
return tot
def DayOfWeek(self):
# 计算当前日期是星期几,其中1为星期一,7为星期日
if not self.isLeagal():
self.GiveTips()
return
self.Week = int(self.day+2*self.month+3*(self.month+1)//5+self.year+self.year//4-self.year//100+self.year//400)%7+1
return self.Week
def __sub__(self, another):
# 重载运算符计算两个日期的日期差
# 要求必须是大日期减去小日期,否则给出提示
# 返回结果为天数
dis = 0
Tmon = 0
Tyear = 0
if not self.isLeagal():
self.GiveTips()
return
if isinstance(another, Date) == False:
return('参数必须为日期对象!')
else:
if not another.isLeagal():
another.GiveTips()
return
if self.year < another.year:
return '仅支持大日期减小日期!'
elif self.year == another.year:
if self.month < another.month:
return '仅支持大日期减小日期!'
elif self.month == another.month:
if self.day < another.day:
return '仅支持大日期减小日期!'
elif self.day == another.day:
# 两个日期为同一天
dis = 0
else:
# 两个日期在同一个月但不是同一天
dis = self.day - another.day
else:
# 两个日期在一年的不同月
dis += another.DayList[another.month] - another.day
Tmon = self.month - another.month - 1
if Tmon == 0:
dis += self.day
else:
for i in range(another.month+1,another.month+1+Tmon):
dis += another.DayList[i]
dis += self.day
else:
# 两个日期在不同年
if another.leap == True:
dis += 366 - another.DayOfYear()
else:
dis += 365 - another.DayOfYear()
for i in range(another.year+1,self.year):
if Date(i,1,1).isLeap() == True:
dis += 366
else:
dis += 365
dis += self.DayOfYear()
return dis
def CalDis(self, another):
# 计算两个日期的日期差
return self.__sub__(another)
def After(self, num):
# 计算从今天开始几天之后的日期
# 返回类型为一个日期对象
if not self.isLeagal():
self.GiveTips()
return
targetday = self.DayOfYear() + num
Nowyear = self.year
Nowmon = self.month
Nowday = self.day
while targetday > Date(Nowyear,Nowmon,Nowday).TOT:
targetday -= Date(Nowyear,Nowmon,Nowday).TOT
Nowyear += 1
TempDate = Date(Nowyear,Nowmon,Nowday)
for i in range(1,13):
if targetday > TempDate.DayList[i]:
targetday -= TempDate.DayList[i]
else:
Nowmon = i
Nowday = targetday
return Date(Nowyear,Nowmon,Nowday)
def PrintMonth(self):
# 打印当前月的日历
if not self.isLeagal():
self.GiveTips()
return
self.PrintMon(self.month)
def PrintMon(self,mon):
# 打印的月历的执行函数
Matrix = []
if not self.isLeagal():
self.GiveTips()
return
for i in range(0,6):
Matrix.append([0,0,0,0,0,0,0])
print(' '+str(mon)+'月月历')
print(' 一 二 三 四 五 六 日')
sta = Date(self.year,mon,1).DayOfWeek()
tag = 1
exit = False
for i in range(sta-1,7):
Matrix[0][i] = tag
tag+=1
for i in range(1,6):
for j in range(0,7):
Matrix[i][j] = tag
tag+=1
if tag > self.DayList[mon]:
exit = True
break
if exit:
break
exit = False
for i in range(0,6):
for j in range(0,7):
if Matrix[i][j] == 0:
print(' ',end = '')
else:
if len(str(Matrix[i][j])) == 1:
print(' ',Matrix[i][j],sep = '',end = '')
else:
print(' ',Matrix[i][j],sep = '',end = '')
if Matrix[i][j] == self.DayList[mon]:
exit = True
print('\n')
if exit:
break
def PrintCalendar(self):
if not self.isLeagal():
self.GiveTips()
return
for i in range(1,13):
self.PrintMon(i)
def Show(self,sep = '-', isDig = True):
if not self.isLeagal():
self.GiveTips()
return
if isDig:
print(str(self.year) + sep + str(self.month) + sep + str(self.day))
else:
temp = str(self.day)
if self.day == 1:
temp+='st'
elif self.day == 2:
temp+='nd'
else:
temp+='th'
print(str(self.year) + sep +self.HashList[self.month]+ sep + temp )
a = Date(2004,3,1)
b = Date(2017,12,21)
print(b - a)
print(b.DayOfYear())
print(b.DayOfWeek())
b.PrintMonth()
print(a.Show(isDig = False))
全文检索引擎
程序设计说明
设在C:\Record下面有若干个.txt文件,均为纯英文文档。以这些文档为内容,实现一个本地搜索引擎,当用户给出某个输入时,列出相关搜索结果。可自行决定改搜索引擎的功能强弱,并给出有关文档说明。
程序设计思路
算法设计
这个题目让我想起了网页搜索引擎。在使用百度、Google等搜索引擎的时候,其实就是输入一些关键字,搜索引擎返回和这些关键字最为匹配的一组结果。只不过网页搜索引擎还要多一点,就是对搜索结果进行排序。也就是说不仅仅会根据文本的内容,还会根据Pagerank,当前话题的热度,热点新闻等等方面,对搜索结果有先有后的输出。
如果说最朴素的搜索,搜索引擎的实质,实际上就是对输入文本,与数据库内文本的进行匹配,找出匹配度最大的记录。这不妨让我想到了数据结构里面学过的BF算法与KMP算法,还有在ACM训练中学到的AC自动机,以及最长子序列等动态规划的问题。对于这当中的绝大部分算法来说,只能精确的搜索某个字符串,返回是否出现,出现的位置或者出现的次数。如果说搜索引擎只是在这些文本上面跑KMP,然后看关键字出现的次数,以出现的次数做降序排列,问题也显而易见。首先,如果输入的不是关键字,而是一个模糊的句子,算法就失效了。其次,对于大量的文本,在上面跑KMP,效率未免太低了。
回归到搜索引擎的使用上来。在我的使用过程中,很少在上面搜索一句
话,一般都是搜索关键字。比如我想知道“如何在 python3 下利用 numpy 实现矩阵的转置”,一般都会搜索“python3 numpy 转置”。而搜索引擎会在数据库内找与这三个关键词最为匹配的,将结果展示出来,而且这两者的搜索结果,并没有很大的差异!
也就是说,就算输入的是完整的句子,其实搜索的时候也还是将句子中的关键字与数据库内中的记录找最为匹配的记录。
而可以发现,搜索关键字与搜索句子相比,得到的结果更少。这也不难理解,因为搜索句子的话,从句子中可以提取的关键字更多,获取到的匹配内容也就随之增多。
所以思路也就来了,只需要模仿搜索引擎,对关键字进行搜索即可。而题设说针对全英文的文本文档,那么这样的话,只需要对单词进行搜索即可。其实即使是对中文进行搜索,只需要输入的句子/关键字执行分词,然后再匹配就可以得到很好的结果。
那么问题也随之而来,如何提取文本的关键字,如何衡量文本的相似程度?很自然的想到,如果一个文章某个词中出现频率越高,那么说明这一个词很重要,极有可能整片文章都是围绕这个关键词来展开的,所以词频(Term Frequency,缩写为TF)可以作为一个统计量来使用。然而对于某一特定文章来说,中世纪可能是他的关键词,但是中世纪出现频率并不高,但是比如希腊又是一个很常见词。如果用户搜索中世纪,显然这篇文章应该出现在搜索结果中。所以中世纪在这篇文章中,即使出现次数很少,但是也应该给与较高的权重,这就需要用”逆文档频率”(Inverse Document Frequency,缩写为IDF)来衡量。
换句话说,用TF-IDF来衡量某一关键词的权值。
提取好了关键词,下面解决如何衡量匹配度的问题。查阅相关资料发现,TF-IDF与余弦相似性是一个不错的组合。
在二维向量空间中,可以通过两个向量的余弦值来计算他们的夹角,夹角越小,说明两个向量方向越相同,若夹角为90°,则说明方向完全不相同,甚至成180°夹角,说明完全相反。数学家已经证明,余弦值的计算对于高维向量依然成立。所以可以用余弦值的大小刻画关键字与文本的相似程度。余弦值越大,说明关键字与文本的相似程度越高。
设语料库中有n篇文章,共有m个关键字。将n篇文章的不同关键字的TF-IDF值做成一个n×m维向量X。
对于输入的搜索关键词,用空格作为分隔符分割,依次判断每个单词是否在出现在语料库中的单词中,若出现过,则将对应位置置为1,否则为0.这样得到一个一维行向量Y。
对于第i篇文章,其相似度可以刻画为:
对于每篇文章,可以采用矩阵运算的方法,来计算其不同的相似程度,最后根据相似程度排序,由高到底展示即可。
在搜索资料的过程中,还发现一个叫做Whoosh的全文检索第三方模块,使用起来也非常好用。
功能实现
程序加载后,首先通过os.walk()遍历文件夹,记录其中后缀为txt的文档的绝对路径和文件名到字典。然后执行提取关键字的部分。提取关键字采用的是sklearn包中sklearn.feature_extraction.text模块的TfidfTransformer()方法,返回结果为语料库词列表word和各文档中词在语料库中的TF-IDF值weight。之后接受用户输入查询的关键字,将关键字以空格做分割,分别匹配关键字是否在语料库词列表word中出现过,若出现则赋值,最终形成一个行向量。按照(4)式计算各关键词在各文章的相似度,根据相似度降序排列展示。\
界面设计
界面设计的比较简单,输入框用来搜索关键字,按钮用来搜索,TreeView用来展示搜索结果。同时TreeView绑定了单击函数,在选中某个项目的时候,下方给出当前文本中匹配了哪些单词。
程序加载是立即对所有文本建立语料库,以及计算出语料库中词在每篇文章中的TF-IDF值。可以看到对于文本的处理还是很快的。
参考资料
python scikit-learn计算tf-idf词语权重
程序代码
textsearchui.py
import os, sys
import json, pyperclip
import textsearch as ts
from tkinter import *
from tkinter.font import Font
from tkinter.ttk import *
from tkinter.messagebox import *
class Application_ui(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master.title('文本搜索器')
self.master.geometry('550x350')
self.LoadFrames()
self.ViewFrame()
def LoadFrames(self):
self.style = Style()
self.top = self.winfo_toplevel()
self.style.configure('VFrame.TLabelframe',font=('宋体',9))
self.VFrame = Frame(self.top, style='VFrame.TLabelframe')
def ViewFrame(self):
self.VFrame.grid(row=0, column=0, padx=0, pady=0)
self.style.configure('Tips.TLabel',anchor='w', font=('宋体',9))
self.TagTips = Label(self.VFrame, text='请输入要查询的关键字', style='Tips.TLabel')
self.TagTips.grid(row = 0, column = 0, sticky = W)
self.KeynameVar = StringVar(value='')
self.Key = Entry(self.VFrame, text='', textvariable=self.KeynameVar, font=('宋体',9))
self.Key.grid(row = 0, column = 0,padx = 150, pady = 5,sticky = NW)
self.style.configure('SearchAll.TButton',font=('宋体',9))
self.SearchAll = Button(self.VFrame, text='搜索', command=self.SearchAll_Cmd, style='SearchAll.TButton')
self.SearchAll.grid(row = 0, column = 0, padx = 300, pady = 3, sticky = NW)
self.Tree = Treeview(self.VFrame, height = 10,show="headings", columns = ('id','simi','name','path',))
self.Tree.column('id', width=45, anchor='center')
self.Tree.column('simi', width=80, anchor='center')
self.Tree.column('name', width=200, anchor='center')
self.Tree.column('path', width=200, anchor='center')
self.Tree.heading('id', text='编号')
self.Tree.heading('simi', text='匹配度')
self.Tree.heading('name', text='文件名称')
self.Tree.heading('path', text='路径')
self.vbar = Scrollbar(self.VFrame, orient=VERTICAL, command=self.Tree.yview)
self.Tree.configure(yscrollcommand=self.vbar.set)
self.Tree.bind('<ButtonRelease-1>',self.Show_Cmd)
self.Tree.grid(row=2, column=0, pady = 3, sticky = NW)
self.vbar.grid(row=2, column=1, pady = 3, sticky = NS)
self.style.configure('Status.TLabel',anchor='w', font=('宋体',9))
self.TagStatus = Label(self.VFrame, text='共加载', style='Status.TLabel')
self.TagStatus.grid(row = 3, column = 0 , pady = 2, sticky = W)
self.style.configure('Match.TLabel',anchor='w', font=('宋体',9))
self.TagMatch = Label(self.VFrame, text='', style='Match.TLabel')
self.TagMatch.grid(row = 4, column = 0 , pady = 2, sticky = W)
def DestroyAll(self):
self.VFrame.destroy()
class Application(Application_ui):
def __init__(self, master=None):
self.totFiles = ts.filesNum
Application_ui.__init__(self, master)
self.id = 1
self.curid = -1
self.Ans = []
self.presentMes = []
ts.WalkDir()
ts.TFIDF()
self.TagStatus['text'] = '共加载' + str(ts.filesNum) + '个文本文件,耗时'+ str(ts.loadtime) +'秒'
def SearchAll_Cmd(self, event = None):
keyword = self.Key.get()
self.Ans = ts.Calcos(keyword)
self.UpdateInfo()
def UpdateInfo(self, event = None):
# 更新搜索信息
self.id = 1
self.presentMes = []
for _ in map(self.Tree.delete, self.Tree.get_children("")):
pass
if self.Ans != None:
for item in self.Ans:
simival, filename, path, originalid = item[0], item[1], item[2], item[3]
if simival == 0:
continue
self.Tree.insert('','end',values = (self.id,simival,filename,path))
self.presentMes.append([originalid])
self.id += 1
self.TagStatus['text'] = '找到'+str(self.id-1)+'条相关信息!'
else:
self.TagStatus['text'] = '找到0条相关信息!'
self.TagMatch['text'] = ''
def Show_Cmd(self, event = None):
self.curid = self.Tree.index(self.Tree.focus())
pos = self.presentMes[self.curid]
MatchList = ts.Getmes(pos)
s = ''
isFirst = True
for i in MatchList:
if isFirst:
s += i
isFirst = False
else:
s += (','+i)
self.TagMatch['text'] = '该条记录成功匹配'+str(len(MatchList))+'个关键词:'+s
if __name__ == '__main__':
top = Tk()
Application(top).mainloop()
try: top.destroy()
except: pass
textsearch.py
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn import feature_extraction
import numpy as np
import time
import os
filesDict = {} # 保存文件的文件名的字典
filesList = [] # 保存文件的文件名
filesNum = 0 # 文件的个数
indexList = []
loadtime = 0
def Calcos(keyword):
# 通过numpy的矩阵运算来计算计算余弦相似度
keyword = keyword.lower()
keywordList = keyword.split(' ')
print(keywordList)
global word
global weight
global filesNum
global indexList
indexList = []
totlen = len(word)
Matrix = [0] * totlen
for k in keywordList:
if k in word:
indexList.append(word.index(k))
if len(indexList) == 0:
return None
val = 1/len(indexList)
print(len(indexList))
for i in indexList:
Matrix[i] = val
keyvec = np.array(Matrix)
temp = np.linalg.norm(keyvec)
keyvec.shape = (totlen,1)
weightvec = np.array(weight)
ans = weightvec.dot(keyvec)
ans.shape = (1,filesNum)
for i in range(filesNum):
ans[0][i] = ans[0][i] / temp
List = []
for i in range(0,filesNum):
List.append([ans[0][i],filesList[i],filesDict[filesList[i]]['Path'],i])
print(ans[0][i],filesList[i])
return reversed(qsort(List))
def Getmes(pos):
global weight
MatchWorldList = []
for i in indexList:
if weight[pos[0]][i] != 0:
MatchWorldList.append(word[i])
return MatchWorldList
def qsort(lst):
# 对最后输出的结果进行排序
if len(lst) <= 1:
return lst
else:
temp1 = [i for i in lst[1:] if i[0] < lst[0][0]]
temp2 = [i for i in lst[1:] if i[0] >= lst[0][0]]
return qsort(temp1) + lst[:1] + qsort(temp2)
def TFIDF():
# 提取文本中的关键字
global word
global weight
corpus = []
word = []
for filename in filesList:
filepath = filesDict[filename]['Path']
with open( filepath ,encoding = 'utf=8') as f:
corpus.append(f.read())
# 提取文本中词语的TF-IDF值
time1 = time.clock()
vectorizer = CountVectorizer()
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))
word = vectorizer.get_feature_names()
weight = tfidf.toarray()
time2 = time.clock()
loadtime = time2 - time1
# for i in range(len(weight)):
# print("-------这里输出第",i,u"类文本的词语tf-idf权重------")
# for j in range(len(word)):
# print(word[j],weight[i][j])
# print(len(weight))
def WalkDir():
# 遍历目录确定文件
rootdir = 'C:\\record'
for parent,dirnames,filenames in os.walk(rootdir):
for filename in filenames:
if filename.endswith('.txt'):
print(filename)
filesList.append(filename)
filesDict[filename] = {'Path':os.path.join(parent,filename)}
global filesNum
filesNum = len(filesList)
if __name__ == '__main__':
WalkDir()
TFIDF()