Python+Qt 使用哈夫曼编码对文本文件进行压缩,解压缩

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_()))







Python+Qt 使用哈夫曼编码对文本文件进行压缩,解压缩_第1张图片

代码+UI+相关工具:

链接:https://pan.baidu.com/s/1dvodyIs_D2UL3e7_jVC3oQ 
提取码:w0la 

 

你可能感兴趣的:(哈夫曼树,哈夫曼编码,压缩,Qt)