from Huffman_Ui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import pygraphviz as pgv
import sys
import numpy as np
import os
import math
import re
DEFAULT_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 = data
self.Char = ch
self.index = index
return
def getchild(self,lc,rc): #确定二叉树的两个孩子
self.lchild = lc
self.rchild = rc
return
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: #如果没有进行操作,直接退出
return
try:
f = open(path[0],'rb') #二进制打开目录下的TXT文件
except FileNotFoundError:
return
if f == None:
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_code_btn(self):
"""功能:打开文本对话框,打开文件,读取信息,显示信息"""
path = QFileDialog().getOpenFileName(self, "Open File", "", "Code (*.code)") #同上
if len(path[0]) == 0:
return
try:
f = open(path[0],'rb')
except FileNotFoundError:
return
if f == None:
return
self.Code_ok = True
self.Code = f.readlines()
f.close()
self.compare_File() #判断是否是该编码类型文件,如果是对其处理
self._update_complex_Comde()
def on_get_btn(self):
"""功能:得到对应的哈夫曼树,显示树图像"""
if not self.Text_ok: #必须要有文本信息
return
self.pre_Process_text() #对文本进行预处理,存储于Char_set中,得到出现的频率
self.get_Tree() #得到对应的哈夫曼树,并形成哈夫曼树图像
self.Show_ok = True #显示ok
self._update() #刷新界面
def on_trun_ordinary_btn(self):
"""功能:转化为简单的哈夫曼编码,原始的01字符串"""
if not self.Show_ok: #必须要有哈夫曼树
return
self.trunTo_Code() #转化为01字符串
self.Pre_encode = True #可以进行下一步操作
self._update_Code() #显示在编码编辑框
def on_trun_complex_btn(self):
"""功能:转化为最终的哈夫曼编码,压缩文件数据"""
if not self.Pre_encode: #必须要对其进行前置操作
return
self.zip_Code() #压缩编码
self.encode = True
self._update_complex_Comde() #显示该编码
def on_save_text_btn(self):
"""功能:保存TEXT文件,不做赘述"""
if not self.Text_ok and not self.decode:
return
path = QFileDialog().getSaveFileName(self, "Save File", "", "TEXT (*.txt)")
if len(path[0]) == 0:
return
text = 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:
return
path = QFileDialog().getSaveFileName(self, "Save File", "", "Code (*.code)")
if len(path[0]) == 0:
return
f = 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:
return
self.decode = True
self.decode_toText() #解码为Text
self._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_()
return
def splitMsg(self):
"""功能:初步划分数据,判断是否是该类型压缩文件"""
regx = re.compile(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 #禁止操作
return
try:
num = int(num[0]) #转换为int类型
except ValueError:
QMessageBox(QMessageBox.Critical,"error!","error filetype").exec_()
self.Code = False
return
self.node_num = num
def 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 = re.compile(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位在其余位置上填充上0
self.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: #如果不存在于字典中,初始化数据为1
self.Char_set[x] = 1
continue #继续循环
self.Char_set[x] += 1 #存在则+1
def 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:
return
dot = pgv.AGraph(directed=False,strict=True)
self.to_HTreeImg(self.Tree,dot) #对该树进行操作
dot.layout('dot')
dot.draw(TEMP_PATH) #存储该缓冲路径
return
def 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 = True
self._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) #将右孩子节点和父节点连接
return
def to_HuffCode(self,hf,str_):
"""功能:递归对所有根节点进行编码"""
if hf.lchild == None and hf.rchild == None:
self.Code_book.update({hf.Char:str_})
return
self.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或1
try:
bnum+=2**(8-x-1) * int(y) #2进制转10进制(8位)
except ValueError:
return
bc.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.lchild
if x == '1':
detree = detree.rchild
if detree.rchild == None and detree.lchild == None:
self.decode_Text += detree.Char
detree = self.Tree
def _update_Text(self):
"""功能:刷新文本信息到界面上"""
text = ""
for s in self.decode_Text:
text += s
self.__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 += s
self.__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) #响应父类的closeEvent
if __name__ == "__main__":
app = QApplication(sys.argv)
ui = Ui_Huffman()
p = Huffman(ui)
sys.exit(app.exit(p.exec_()))
代码+UI+相关工具:
链接:https://pan.baidu.com/s/1dvodyIs_D2UL3e7_jVC3oQ
提取码:w0la