from Huffman_Ui import *from PyQt5.QtWidgets import *from PyQt5.QtCore import *from PyQt5.QtGui import * import pygraphviz as pgvimport sysimport numpy as npimport osimport mathimport reDEFAULT_TEXT = "Mesg.txt" #固定路径,起始路径TEMP_PATH = "temp1.jpg"#缓冲图像class HTree:lchild = None#二叉树的左孩子rchild = None#二叉树的右孩子data = 0 #所占比例Char = ''#字符index = 0#索引def __init__(self,data,ch,index):#二叉树的构造函数self.data = dataself.Char = chself.index = indexreturndef getchild(self,lc,rc): #确定二叉树的两个孩子self.lchild = lcself.rchild = rcreturn class Huffman(QDialog):def __init__(self,ui,parent = None):super().__init__(parent)self._Qimg = QPixmap() #用于显示图片self.Text = [] #文本二进制数据(二进制打开)self.decode_Text = [] #文本解码后数据self.Code = [] #压缩哈夫曼编码(二进制)self.decode_Code = [] #压缩哈夫曼编码(无头部信息,解码后数据)self.Tree = None #哈夫曼树self.index = 0 #节点索引self.Text_ok = False #文本OK(可以对其进行操作)self.Show_ok = False #显示哈夫曼树OK(可以对哈夫曼树进行初步编码)self.Pre_encode = False#初步编码OK(编码成01格式的字符串)self.encode = False #最终编码OK(可以对文本操作)self.Code_ok = False #编码OK(可以对文本操作)self.decode = False #解码OK(必须要编码OK)self.node_num = 0#根节点个数self.Header_Msg = "" #头部信息self.Char_set = {}#字符集合self.Code_book = {} #密码表self.__ui = ui #ui界面self.__scaledImg = None #防止图片过大,缩放后的图片self.__ui.setupUi(self) #给界面上控件self.__ui.ImgLabel.setAlignment(Qt.AlignCenter) #设置对其方式:居中对齐"""关联对应控件"""self.__ui.open_text_btn.clicked.connect(self.on_open_text_btn) self.__ui.open_code_btn.clicked.connect(self.on_open_code_btn)self.__ui.get_btn.clicked.connect(self.on_get_btn)self.__ui.trun_ordinary_btn.clicked.connect(self.on_trun_ordinary_btn)self.__ui.trun_complex_btn.clicked.connect(self.on_trun_complex_btn)self.__ui.save_code_btn.clicked.connect(self.on_save_code_btn)self.__ui.save_text_btn.clicked.connect(self.on_save_text_btn)self.__ui.decode_btn.clicked.connect(self.on_decode_btn)"""初始化读取文本"""self.init()def init(self):"""初始化,给界面添加文本,以便于直接操作"""try:f = open(DEFAULT_TEXT,'rb') #打开固定目录下的TXT文件except FileNotFoundError:return #文件不存在,退出self.Text = f.readlines() #读取文本信息f.close() #关闭文件self.Text_ok = True #可以对文本进行操作for i in range(len(self.Text)):self.decode_Text.append(self.Text[i].decode()) #解码后的文本显示在界面上self._update_Text() #刷新def on_open_text_btn(self):"""功能:打开文本对话框,打开文件,读取信息,显示信息"""path = QFileDialog().getOpenFileName(self, "Open File", "", "TEXT (*.txt)") #文件对话框,打开文件目录,得到文件路径if len(path[0]) == 0: #如果没有进行操作,直接退出returntry:f = open(path[0],'rb') #二进制打开目录下的TXT文件except FileNotFoundError:returnif f == None:returnself.Text = f.readlines() #得到文本信息f.close() #同上self.Text_ok = Truefor i in range(len(self.Text)):self.decode_Text.append(self.Text[i].decode())self._update_Text()def on_open_code_btn(self):"""功能:打开文本对话框,打开文件,读取信息,显示信息"""path = QFileDialog().getOpenFileName(self, "Open File", "", "Code (*.code)") #同上if len(path[0]) == 0:returntry:f = open(path[0],'rb') except FileNotFoundError:returnif f == None:returnself.Code_ok = Trueself.Code = f.readlines()f.close()pare_File() #判断是否是该编码类型文件,如果是对其处理self._update_complex_Comde()def on_get_btn(self):"""功能:得到对应的哈夫曼树,显示树图像"""if not self.Text_ok:#必须要有文本信息returnself.pre_Process_text() #对文本进行预处理,存储于Char_set中,得到出现的频率self.get_Tree() #得到对应的哈夫曼树,并形成哈夫曼树图像self.Show_ok = True #显示okself._update() #刷新界面def on_trun_ordinary_btn(self): """功能:转化为简单的哈夫曼编码,原始的01字符串"""if not self.Show_ok:#必须要有哈夫曼树returnself.trunTo_Code() #转化为01字符串self.Pre_encode = True #可以进行下一步操作self._update_Code() #显示在编码编辑框def on_trun_complex_btn(self):"""功能:转化为最终的哈夫曼编码,压缩文件数据"""if not self.Pre_encode: #必须要对其进行前置操作returnself.zip_Code() #压缩编码self.encode = Trueself._update_complex_Comde() #显示该编码def on_save_text_btn(self):"""功能:保存TEXT文件,不做赘述"""if not self.Text_ok and not self.decode: returnpath = QFileDialog().getSaveFileName(self, "Save File", "", "TEXT (*.txt)")if len(path[0]) == 0:returntext = self.__ui.o_text.toPlainText()self.Text.clear()for x in text:self.Text.append(x.encode())f = open(path[0],'wb')f.writelines(self.Text)f.close()def on_save_code_btn(self):"""功能:保存CODE文件,不做赘述"""if not self.Code_ok and not self.encode:returnpath = QFileDialog().getSaveFileName(self, "Save File", "", "Code (*.code)")if len(path[0]) == 0:returnf = open(path[0],'wb')f.write(self.Code[0])f.write(self.Code[1])f.close()def on_decode_btn(self):"""功能:对压缩文件进行解码"""if not self.Code_ok:returnself.decode = Trueself.decode_toText()#解码为Textself._update_Text() #显示在Text编辑框中self._update() #显示对应哈夫曼树def compare_File(self):"""功能:对压缩文件进行读取,初步解码"""try:self.splitMsg() #划分头部信息和数据信息self.scan_File()#扫描数据信息,读取对应的哈夫曼编码self.get_Tree() #得到对应的哈夫曼树except UnicodeDecodeError:QMessageBox(QMessageBox.Critical,"error!","error filetype").exec_()returndef splitMsg(self):"""功能:初步划分数据,判断是否是该类型压缩文件"""regx = pile(r'^\d+')#匹配根节点个数code = self.Code[0] #获取头部信息self.Header_Msg = code.decode()#对头部信息解码,得到对应字符的出现频率(更容易对其进行解码操作)num = regx.search(self.Header_Msg) #得到对应的根节点个树if num == None: #如果获取失败,则错误退出QMessageBox(QMessageBox.Critical,"error!","error filetype").exec_()self.Code = False#禁止操作returntry:num = int(num[0])#转换为int类型except ValueError:QMessageBox(QMessageBox.Critical,"error!","error filetype").exec_()self.Code = Falsereturnself.node_num = numdef SettoChar(self,num):"""功能:将8字节数据转换为字符串"""if num > 1:return self.SettoChar(int(num/2))+str(num%2)else:return str(num)def scan_File(self):"""功能:扫描文件读取头部信息和数据信息"""regx = pile(r'(\d+):(\d+),') #匹配格式字符串(正则表达式)code = regx.findall(self.Header_Msg) #查找所有满足格式的字符串for x,y in code: self.Char_set.update({chr(int(x)):int(y)}) #字符串转为int类型,在转换为字符类型,x表示字符,y表示出现次数b = self.Code[1:] #得到对应的数据信息self.decode_Code = "" #清空压缩文件的解码信息for y in b:for x in y: #对每个字节数据转换为01字符串temp_c = self.SettoChar(x) #转换为01字符串cpt = 8-len(temp_c) #判断是否填满8位ge_ch = cpt *"0" + temp_c#不满足8位在其余位置上填充上0self.decode_Code += ge_ch#存储def pre_Process_text(self):"""功能:判断是否有文字输入"""self.decode_Text = self.__ui.o_text.toPlainText()if len(self.decode_Text) == 0:QMessageBox(QMessageBox.Critical,"error!","Please input with some words").exec_()#提醒需要输入文本return"""功能:填入字典中,得到其出现频率"""for y in self.decode_Text: for x in y:if x not in self.Char_set: #如果不存在于字典中,初始化数据为1self.Char_set[x] = 1continue#继续循环self.Char_set[x] += 1 #存在则+1def get_Tree(self):"""功能:构造节点"""ht = []for x,y in self.Char_set.items():ht.append(HTree(y,x,self.index))#y中为二叉树节点的data,x是字符self.index += 1"""功能:构造哈夫曼树"""while len(ht)>1:ht = sorted(ht,key = lambda x:x.data)#用该函数给节点按照数值排序hf = HTree(ht[0].data + ht[1].data,"",self.index) #新生成的节点的数值为其孩子节点数值的和self.index += 1 #索引更新hf.getchild(ht[0],ht[1]) #连接孩子节点ht.pop(0) #将最前面的节点弹出ht.pop(0)ht.append(hf) #将新节点添加至链表中self.Tree = ht[0]self.show_HTreeImg()#形成对应的哈夫曼编码图像def show_HTreeImg(self):""""功能:用pygraphviz形成二叉树图像"""if self.Tree == None:returndot = pgv.AGraph(directed=False,strict=True)self.to_HTreeImg(self.Tree,dot)#对该树进行操作dot.layout('dot')dot.draw(TEMP_PATH) #存储该缓冲路径returndef trunTo_Code(self):""""功能:递归查询所有节点,并记录其哈夫曼编码,将文本信息转化为哈夫曼编码(01字符串)"""self.to_HuffCode(self.Tree,"")for y in self.decode_Text:code = ""for x in y:code += self.Code_book[x]self.decode_Code.append(code)self.Code_ok = Trueself._update()def to_HTreeImg(self,bt,dot):"""功能:调用库中的函数操作哈夫曼树图像"""if(bt == None): return #判断节点是否存在dot.add_node(bt.index,label = "{}".format(bt.Char)) #添加节点,label 是节点显示的内容,保留两位有效数字self.to_HTreeImg(bt.lchild,dot) #递归添加左孩子节点self.to_HTreeImg(bt.rchild,dot) #递归添加右孩子节点if(bt.lchild != None): #判断孩子节点是否存在dot.add_edge(bt.index,bt.lchild.index) #将左孩子节点和父节点连接if(bt.rchild != None): #判断孩子节点是否存在dot.add_edge(bt.index,bt.rchild.index) #将右孩子节点和父节点连接returndef to_HuffCode(self,hf,str_):"""功能:递归对所有根节点进行编码"""if hf.lchild == None and hf.rchild == None:self.Code_book.update({hf.Char:str_})returnself.to_HuffCode(hf.lchild,str_+"0")self.to_HuffCode(hf.rchild,str_+"1")def zip_Code(self):"""功能:添加头部信息,添加数据压缩信息,压缩数据"""self.add_header() #添加头部信息self.add_Code()#添加压缩编码信息def add_header(self):"""功能:添加头部信息"""self.Header_Msg = ""self.Header_Msg += str(len(self.Code_book))self.Header_Msg += " "for x,y in self.Char_set.items():"""头部信息存储格式(采用ASCII码存储而不是原字符存储,因为字符存储的话,会有换行符,不便于操作)"""self.Header_Msg += str(ord(x)) +":"+ str(y) +',' self.Code.append(self.Header_Msg.encode() + b'\n')#换行利于读取头部信息def add_Code(self):"""功能:将01字符串转换为字节形式存储"""str_ = "" #处理的01字符串bc = [] #存储的字节信息bnum = 0 #单个字节for x in self.decode_Code:str_ += x #汇总while True:bs = str_[:8] #切片8个字符 str_ = str_[8:] #更新字符串长度if len(bs) == 0: break #退出条件else:for x,y in enumerate(bs):#x是位置,y是0或1try:bnum+=2**(8-x-1) * int(y) #2进制转10进制(8位)except ValueError:returnbc.append(bnum)bnum = 0 self.Code.append(bytes(bc)) #添加字节信息def decode_toText(self):"""功能:将01字符串转换为文本信息"""self.decode_Text = ""detree = self.Tree"""对每一个字符进行检索,判断是否为根节点,如果是则添加该字符到文本信息存储"""for x in self.decode_Code: if x == '0':detree = detree.lchildif x == '1':detree = detree.rchildif detree.rchild == None and detree.lchild == None:self.decode_Text += detree.Chardetree = self.Treedef _update_Text(self):"""功能:刷新文本信息到界面上"""text = ""for s in self.decode_Text:text += sself.__ui.o_text.setPlainText(text)def _update_complex_Comde(self):"""功能:刷新压缩信息到界面上"""text = ""for s in self.Code:text += str(s)self.__ui.p_code.setPlainText(text)def _update_Code(self):"""功能:刷新编码信息到界面上"""text = ""for s in self.decode_Code:text += sself.__ui.p_code.setPlainText(text)def _update(self):"""功能:更新图片"""self.__showGraph() #对图片的处理if os.path.exists(TEMP_PATH): #是否存在缓存图片self._Qimg.load(TEMP_PATH)#加载图片if self._Qimg.size().width() > self.__ui.ImgLabel.size().width() and \self._Qimg.size().height() > self.__ui.ImgLabel.size().height():#图片过大则缩放图片self.__scaledImg = self._Qimg.scaled(self.__ui.ImgLabel.size())elif self._Qimg.size().width() > self.__ui.ImgLabel.size().width(): #根据宽缩放self.__scaledImg = self._Qimg.scaledToWidth(self.__ui.ImgLabel.size().width())elif self._Qimg.size().height() > self.__ui.ImgLabel.size().height():#根据高缩放self.__scaledImg = self._Qimg.scaledToHeight(self.__ui.ImgLabel.size().height())else:self.__scaledImg = self._Qimg.copy()#复制该图片信息self.__ui.ImgLabel.setPixmap(self.__scaledImg)#给Label贴上图片super().update() #调用父类的update函数def __showGraph(self): #显示图像self.show_HTreeImg()def closeEvent(self, event):#当界面关闭的时候响应该函数if os.path.exists(TEMP_PATH): #判断是否存在缓冲图像os.remove(TEMP_PATH)#删除该图像super().closeEvent(event)#响应父类的closeEventif __name__ == "__main__":app = QApplication(sys.argv)ui = Ui_Huffman()p = Huffman(ui)sys.exit(app.exit(p.exec_()))
代码+UI+相关工具:
链接:/s/1dvodyIs_D2UL3e7_jVC3oQ
提取码:w0la
如果觉得《Python+Qt 使用哈夫曼编码对文本文件进行压缩 解压缩》对你有帮助,请点赞、收藏,并留下你的观点哦!