1, Title
Huffman codec
2, Experimental purpose
- Master Huffman coding principle.
- Master the generation method of Huffman tree.
- Understand the implementation of data coding, compression and decoding output coding.
3, Demand analysis
- Initialization. Read the character set size n, n characters and N weights from the terminal, establish the Huffman tree, and store it in the file hfmTree.
- Encoding. Using the built Huffman tree (if it is not in memory, read it from the file hfmTree), encode the text in the file ToBeTran, and then store the results in the file CodeFile.
- Decoding. The code in the file CodeFile is decoded by using the built Huffman tree, and the results are stored in the file Textfile.
- Print the code file (print). The file CodeFile is displayed on the terminal in a compact format, with 50 codes per line. At the same time, the encoding file in the form of this character is written into the file CodePrin.
- Print Huffman Tree printing. Display the Huffman tree already in memory on the terminal in an intuitive way (such as tree), and write the Huffman tree in this character form into the file TreePrint.
- Network communication. Network communication is carried out in the form of Huffman coding as an encryption method.
4, Outline design
5, Program description
- Run with source file
① First install PyQt5 and graphviz libraries and https://graphviz.gitlab.io/download/ Install dot
② Then open the command line under the directory and enter the command: python Huffman codec py
Note: python version should be greater than 3.8 - Use the packaged exe file
Double click Huffman codec exe
However, in both methods, the corresponding files and ui folders must be placed in the same directory
6, Detailed design
(1) Initialization
There are two forms of initialization: one is generated directly from the original text, and the other is based on the direct import of characters. On the basis of these two methods, you can also add, delete, modify and query the character set in the list, and save the file for the existing character set. Finally, when closing the window, the program will build a tree according to the existing character set
- Generated according to the original text
def add(self): # Add a blank line self.tableWidget.insertRow(self.tableWidget.rowCount()) def generateCharacterSetFromRawtext(self): # Generate character set from original text self.tableWidget.clearContents() self.tableWidget.setRowCount(0) def getFrequency(text: str) -> dict: # Word frequency (Statistics) cnt = {} for i in text: if i not in cnt: cnt[i] = 1 else: cnt[i] += 1 return cnt CharacterSet = getFrequency(rawTextEdit.toPlainText()) for i, j in CharacterSet.items(): self.add() item1 = QTableWidgetItem(i) item2 = QTableWidgetItem(str(j)) self.tableWidget.setItem(self.tableWidget.rowCount()-1, 0, item1) self.tableWidget.setItem(self.tableWidget.rowCount()-1, 1, item2)
- Import characters directly
def importWordFrequency(self): # Import word frequency filePath, ok = QFileDialog.getOpenFileName(self, 'Select file') if ok: self.tableWidget.clearContents() self.tableWidget.setRowCount(0) with open(filePath, 'r', encoding='utf-8') as file: try: frequency = file.read() except UnicodeDecodeError: QMessageBox.critical( self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.OK) return global CharacterSet CharacterSet = {} textlines = re.findall(r'([\s\S])\t(\S+)(\n|$)', frequency) if len(textlines) == 0: QMessageBox.critical(self, "error", "Character set generation failed", QMessageBox.Ok) return for i, j, _ in textlines: try: CharacterSet[i] = float(j) except ValueError: QMessageBox.critical( self, "error", "Character set generation failed", QMessageBox.Ok) self.tableWidget.clearContents() self.tableWidget.setRowCount(0) CharacterSet = {} return self.add() item1 = QTableWidgetItem(i) item2 = QTableWidgetItem(j) self.tableWidget.setItem( self.tableWidget.rowCount()-1, 0, item1) self.tableWidget.setItem( self.tableWidget.rowCount()-1, 1, item2)
- Additional additions, deletions and modifications
def add(self): # Add a blank line self.tableWidget.insertRow(self.tableWidget.rowCount()) def find(self): # Find characters or word frequencies or characters and word frequencies a: str = self.wordFrequencyEdit.text() b: str = self.frequencyEdit.text() i: int = 0 if a and b: while i < self.tableWidget.rowCount(): if self.tableWidget.item(i, 0).text() == a and self.tableWidget.item(i, 1).text() == b: self.resultLabel.setText(str(i+1)) break i += 1 elif not a and b: while i < self.tableWidget.rowCount(): if self.tableWidget.item(i, 1).text() == b: self.resultLabel.setText(str(i+1)) break i += 1 elif a and not b: while i < self.tableWidget.rowCount(): if self.tableWidget.item(i, 0) and self.tableWidget.item(i, 0).text() == a: self.resultLabel.setText(str(i+1)) break i += 1 if i == self.tableWidget.rowCount(): self.resultLabel.setText("not found")
- Save word frequency
def saveWordFrequency(self): # Save file filePath, ok = QFileDialog.getSaveFileName(self, 'Select file') if ok: with open(filePath, 'w', encoding='utf-8') as file: for i in range(self.tableWidget.rowCount()): m = '\t'.join([self.tableWidget.item( i, 0).text(), self.tableWidget.item(i, 1).text()]) file.write(m+'\n')
- Build a tree
def generateCharacterSetFromRawtext(self): # Generate character set from original text self.tableWidget.clearContents() self.tableWidget.setRowCount(0) def getFrequency(text: str) -> dict: # Word frequency (Statistics) cnt = {} for i in text: if i not in cnt: cnt[i] = 1 else: cnt[i] += 1 return cnt CharacterSet = getFrequency(rawTextEdit.toPlainText()) for i, j in CharacterSet.items(): self.add() item1 = QTableWidgetItem(i) item2 = QTableWidgetItem(str(j)) self.tableWidget.setItem(self.tableWidget.rowCount()-1, 0, item1) self.tableWidget.setItem(self.tableWidget.rowCount()-1, 1, item2) def closeEvent(self, event): # Closing Windows if self.tableWidget.rowCount() == 0: return global CharacterSet CharacterSet = {} # Save the character set in the table into the variable CharacterSet for i in range(self.tableWidget.rowCount()): if self.tableWidget.item(i, 0) and self.tableWidget.item(i, 1): try: CharacterSet[self.tableWidget.item(i, 0).text()] = float( self.tableWidget.item(i, 1).text()) except: pass global HFTree # Update the tree based on the existing character set if CharacterSet != {}: HFTree = HuffmanTree(CharacterSet) global showSVGWidget if showSVGWidget: HFTree.printTree('tmp') showSVGWidget.update() paintTreeWindow.printInform()
(2) Encoding
Encoding encodes the original text according to the existing tree in memory, and there are two ways to read the original text, one is manual input, the other is to read the file, and the original text can also be saved
- code
def encode(self, text: str) -> str: # Encode text in text p, q = '', '' # p is the code of each character and q is the code of the whole article for i in text: for j in self.nodes: if i == j.name: while j.parent: if j.parent.lchild == j: p += '0' elif j.parent.rchild == j: p += '1' j = j.parent q += p[::-1] p = '' break else: # If the current character is not in the character set, an empty ciphertext is returned return None return q def encoding(self): if not HFTree: QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok) elif rawTextEdit.toPlainText() == '': QMessageBox.critical(self, "error", "Please enter the original text", QMessageBox.Ok) else: t = HFTree.encode(rawTextEdit.toPlainText()) if not t: QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return self.encodedTextEdit.setText(t)
- File read in
def encodeFileReadin(self): filePath, ok = QFileDialog.getOpenFileName(self, 'Select file') if ok: with open(filePath, 'r', encoding='utf-8') as file: try: text = file.read() except UnicodeDecodeError: QMessageBox.critical( self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok) return self.rawTextEdit.setText(text)
- ③ Save original text
def saveRawTextContent(self): filePath, ok = QFileDialog.getSaveFileName(self, 'Select file') if ok: with open(filePath, 'w', encoding='utf-8') as file: file.write(self.rawTextEdit.toPlainText())
(3) Decoding
Decoding is to decode the ciphertext according to the existing tree in memory, and there are two ways to read the ciphertext, one is manual input, the other is to read the file, and the ciphertext can also be saved
- decoding
def decode(self, text: str) -> str: # Decode the 01 string in text in the tree root: TreeNode = self.rootnode result = "" for i in text: if i == '0': root = root.lchild elif i == '1': root = root.rchild elif i == '\n': # '\ n' in compact format needs to be ignored continue else: return None if root.name: result += root.name root = self.rootnode if root != self.rootnode: return None else: return result def decoding(self): if not HFTree: QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok) elif self.encodedTextEdit.toPlainText() == '': QMessageBox.critical(self, "error", "Please enter the ciphertext", QMessageBox.Ok) else: t = HFTree.decode(self.encodedTextEdit.toPlainText()) if not t: QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return self.rawTextEdit.setText(t)
- File read in
def decodeFileReadin(self): filePath, ok = QFileDialog.getOpenFileName(self, 'Select file') if ok: with open(filePath, 'r', encoding='utf-8') as file: try: encodedTextEdit = file.read() except UnicodeDecodeError: QMessageBox.critical( self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok) return if not checkDecodedText(encodedTextEdit): QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return self.encodedTextEdit.setText(encodedTextEdit) ③ Save ciphertext def saveEncodedTextContent(self): if not checkDecodedText(self.encodedTextEdit.toPlainText()): QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return filePath, ok = QFileDialog.getSaveFileName(self, 'Select file') if ok: with open(filePath, 'w', encoding='utf-8') as file: file.write(self.encodedTextEdit.toPlainText())
(4) Print code file (print)
The requirement here is to output in compact format and store files
- Compact format
def compactFormPrint(self): Text = self.encodedTextEdit.toPlainText() text = '' m = 50 for i in Text.replace('\n', ''): text += i m -= 1 if m == 0: text += '\n' m = 50 self.encodedTextEdit.setPlainText(text)
- storage
def saveEncodedTextContent(self): if not checkDecodedText(self.encodedTextEdit.toPlainText()): QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return filePath, ok = QFileDialog.getSaveFileName(self, 'Select file') if ok: with open(filePath, 'w', encoding='utf-8') as file: file.write(self.encodedTextEdit.toPlainText())
(5) Print Huffman Tree printing
Print Huffman tree, including spanning tree information, image operation in the control, tree information display, tree import and storage, and related operations of viewing character set
- Picture of spanning tree
def printTree(self, filename=None): # Picture of spanning tree dot = Digraph(comment="Generated tree") dot.attr('node', fontname="STXinwei", shape='circle', fontsize="20") for i, j in enumerate(self.nodes): if j.name == '' or not j.name: dot.node(str(i), '') elif j.name == ' ': dot.node(str(i), '[ ]') # Spaces are displayed as' [] ' elif j.name == '\n': dot.node(str(i), '\\\\n') # The newline character is shown as' \ n 'escape, where it will also be called, so four slashes are required elif j.name == '\t': dot.node(str(i), '\\\\t') # The tab displays as' \ t ' else: dot.node(str(i), j.name) dot.attr('graph', rankdir='LR') for i in self.nodes[::-1]: if not (i.rchild or i.lchild): break if i.lchild: dot.edge(str(self.nodes.index(i)), str( self.nodes.index(i.lchild)), '0', constraint='true') if i.rchild: dot.edge(str(self.nodes.index(i)), str( self.nodes.index(i.rchild)), '1', constraint='true') dot.render(filename, view=False, format='svg')
- Zoom in and out of tree image
class ShowSVGWidget(QWidget): # Custom control to display svg pictures leftClick: bool svgrender: QSvgRenderer defaultSize: QSizeF point: QPoint scale = 1 def __init__(self, parent=None): super().__init__(parent) self.parent = parent # Construct a blank svg image self.svgrender = QSvgRenderer( b'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 0 0" width="512pt" height="512pt"></svg>') # Get picture default size self.defaultSize = QSizeF(self.svgrender.defaultSize()) self.point = QPoint(0, 0) self.scale = 1 def update(self): # Update picture self.svgrender = QSvgRenderer("tmp.svg") self.defaultSize = QSizeF(self.svgrender.defaultSize()) self.point = QPoint(0, 0) self.scale = 1 self.repaint() def paintEvent(self, a0: QtGui.QPaintEvent) -> None: # Painting event (callback function) painter = QPainter() # paint brush painter.begin(self) self.svgrender.render(painter, QRectF( self.point, self.defaultSize*self.scale)) # svg renderer to paint, (brush, qrectf (position, size)) (F means float) painter.end() def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: # Mouse movement event (callback function) if self.leftClick: self.endPos = a0.pos()-self.startPos self.point += self.endPos self.startPos = a0.pos() self.repaint() def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: # Mouse click event (callback function) if a0.button() == Qt.LeftButton: self.leftClick = True self.startPos = a0.pos() def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None: # Mouse release event (callback function) if a0.button() == Qt.LeftButton: self.leftClick = False def wheelEvent(self, a0: QtGui.QWheelEvent) -> None: # Zoom the image according to the position of the cursor oldScale = self.scale if a0.angleDelta().y() > 0: # enlarge if self.scale <= 5.0: self.scale *= 1.1 elif a0.angleDelta().y() < 0: # narrow if self.scale >= 0.2: self.scale *= 0.9 self.point = a0.pos()-(self.scale/oldScale*(a0.pos()-self.point)) self.repaint()
- Display of tree information
def printInform(self): # Update tree information self.treeHeightlabel.setText(str(self.TreeDepth(HFTree))) self.nodeCountlabel.setText(str(len(HFTree.characterset)*2-1)) self.leafCountlabel.setText(str(len(HFTree.characterset)))
- Tree file reading
def importtree(self): # Import the tree information into the picture filePath, ok = QFileDialog.getOpenFileName(self, 'Select file') if ok: with open(filePath, 'r', encoding='utf-8') as file: try: text = file.read() except UnicodeDecodeError: QMessageBox.critical( self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok) return global CharacterSet CharacterSet = self.CharacterSet textlines = re.findall(r'([\s\S])\t(\S+)\t\S+(\n|$)', text) # Reset the character set information after import and update the tree in memory for i, j, _ in textlines: CharacterSet[i] = float(j) global HFTree if CharacterSet != {}: HFTree = HuffmanTree(CharacterSet) global showSVGWidget HFTree.printTree('tmp') showSVGWidget.update() self.printInform() # Write the tree information on the panel
- Tree storage
def savetree(self): # Save tree information filePath, ok = QFileDialog.getSaveFileName(self, 'Select file') if ok: with open(filePath, 'w', encoding='utf-8') as file: for i, j in HFTree.characterset.items(): m = '\t'.join([i, str(j), HFTree.encode(i)]) file.write(m+'\n')
(6) Network communication
Network communication includes server listening interface and waiting for connection, client establishing connection, tree and ciphertext transmission, waiting for reception and disconnection
- Server listening port
def buildServerConnection(self): # Server listening port try: # Get port number port = int(self.lineEdit.text()) except ValueError: QMessageBox.critical(self, "error", "There is currently no port number entered", QMessageBox.Ok) return # Create a socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: # Set the listening port and listen s.bind(("0.0.0.0", port)) s.listen() except OSError: QMessageBox.critical(self, "error", "The port is already occupied", QMessageBox.Ok) return # Waiting for client connection self.stateLabel.setText("Waiting for connection") # Start a new thread to wait for the connection to prevent the program from blocking, and use the daemon flag to automatically end all threads with this flag when the main thread ends threading.Thread(target=self.handleClient, args=[s], daemon=True).start()
- Server waiting for connection
def handleClient(self, s: socket.socket): # Server side waiting for connection c = s.accept()[0] self.stateLabel.setText("Connected") self.s = c # Start the thread waiting to receive threading.Thread(target=self.waitRecv, args=[c], daemon=True).start()
- Client establish connection
def buildClientConnection(self): # Client establish connection try: # Get IP address ip = self.connectIpEditText.text() if ip == None: QMessageBox.critical( self, "error", "There are currently no entered IP address", QMessageBox.Ok) return port = int(self.connectPortEditText.text()) except ValueError: QMessageBox.critical(self, "error", "There is currently no port number entered", QMessageBox.Ok) return s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((ip, port)) except ConnectionRefusedError: QMessageBox.critical(self, "error", "connection failed", QMessageBox.Ok) return except OSError: QMessageBox.critical(self, "error", "IP Or port error", QMessageBox.Ok) return self.stateLabel.setText("Connected") self.s = s # The connection is successful. Start the thread waiting to receive threading.Thread(target=self.waitRecv, args=[s], daemon=True).start()
- Tree and ciphertext transmission
def sendTree(self): # Send tree if not self.s: QMessageBox.critical(self, "error", "Please establish a connection first", QMessageBox.Ok) return global CharacterSet if not CharacterSet or not HFTree: QMessageBox.critical(self, "error", "The current tree is empty", QMessageBox.Ok) return content = 't' # Flag of sending tree for i, j in CharacterSet.items(): content += i+"\t"+str(j)+'\n' # Convert it to Byte for sending self.s.sendall(content.encode()) QMessageBox.information(self, "Tips", "Sent successfully", QMessageBox.Ok) def sendText(self): # Send ciphertext if not self.s: QMessageBox.critical(self, "error", "Please establish a connection first", QMessageBox.Ok) return global encodedTextEdit content = encodedTextEdit.toPlainText() if not checkDecodedText(content): QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return self.s.sendall(('c'+content).encode()) QMessageBox.information(self, "Tips", "Sent successfully", QMessageBox.Ok)
- Waiting for connection
def waitRecv(self, s: socket.socket): # Waiting to accept thread try: while True: data = s.recv(10000000) # Convert content to str type data = data.decode() if data[0] == 't': data = data[1:] textlines = re.findall(r'([\s\S])\t(\S+)(\n|$)', data) global CharacterSet CharacterSet = {} for i, j, _ in textlines: try: CharacterSet[i] = float(j) except ValueError: self.stateLabel.setText("Useless data received") self.tableWidget.clearContents() self.tableWidget.setRowCount(0) CharacterSet = {} return global HFTree if CharacterSet != {}: HFTree = HuffmanTree(CharacterSet) self.stateLabel.setText("Tree received") else: self.stateLabel.setText("Empty tree received") elif data[0] == 'c': self.stateLabel.setText("Ciphertext received") data = data[1:] self.setEncodedTextSign.emit(data) else: self.stateLabel.setText("Useless data received") except ConnectionResetError: # The other party disconnected self.stateLabel.setText("Disconnected") self.s = None except ConnectionAbortedError: # Disconnect yourself pass
- Disconnect
def breakConnection(self): # Disconnect button event try: self.s.close() self.s = None self.stateLabel.setText("Not connected") except: pass
The following is the source code:
from PyQt5 import QtGui from PyQt5.QtWidgets import * from PyQt5.uic import loadUi from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtSvg import * from typing import Dict, List from graphviz import Digraph import sys import os import socket import re import threading import multiprocessing class TreeNode: parent: 'TreeNode' = None # Parent node lchild: 'TreeNode' = None # Left node rchild: 'TreeNode' = None # Right node weight: float = None # Weight, i.e. word frequency name: str = None # Name, i.e. character def __init__(self, name=None, weight=None) -> None: self.name = name self.weight = weight class HuffmanTree: rootnode: TreeNode = None # Root node nodes: List[TreeNode] = None # List of nodes characterset: Dict[str, float] = None # Character set dictionary def __init__(self, characterset: Dict[str, float]): if characterset == {} or characterset == None: # If the character set is empty or there is no character set, return directly without constructing the tree return # To build a tree, first write it in a list nodes: List[TreeNode] = [TreeNode(key, value) for key, value in characterset.items()] +\ [TreeNode() for _ in range(len(characterset)-1)] for i in range(len(characterset), len(nodes)): x = self.select(i, nodes) nodes[x].parent = nodes[i] y = self.select(i, nodes) nodes[y].parent = nodes[i] nodes[i].lchild = nodes[x] nodes[i].rchild = nodes[y] nodes[i].weight = nodes[x].weight+nodes[y].weight self.rootnode = nodes[-1] self.nodes = nodes self.characterset = characterset def select(self, k: int, nodes: List[TreeNode]) -> int: # Find the node with the smallest weight and no parent node in the first k nodes, and return the minimum value x: int = None for i in range(k): if not nodes[i].parent: # If you find the first node without a parent node, mark it with an x x = i break for j in range(x, k): # After node x if nodes[j].weight < nodes[x].weight and not nodes[j].parent: x = j break return x def encode(self, text: str) -> str: # Encode text in text p, q = '', '' # p is the code of each character and q is the code of the whole article for i in text: for j in self.nodes: if i == j.name: while j.parent: if j.parent.lchild == j: p += '0' elif j.parent.rchild == j: p += '1' j = j.parent q += p[::-1] p = '' break else: # If the current character is not in the character set, an empty ciphertext is returned return None return q def decode(self, text: str) -> str: # Decode the 01 string in text in the tree root: TreeNode = self.rootnode result = "" for i in text: if i == '0': root = root.lchild elif i == '1': root = root.rchild elif i == '\n': # '\ n' in compact format needs to be ignored continue else: return None if root.name: result += root.name root = self.rootnode if root != self.rootnode: return None else: return result def printTree(self, filename=None): # Picture of spanning tree dot = Digraph(comment="Generated tree") dot.attr('node', fontname="STXinwei", shape='circle', fontsize="20") for i, j in enumerate(self.nodes): if j.name == '' or not j.name: dot.node(str(i), '') elif j.name == ' ': dot.node(str(i), '[ ]') # Spaces are displayed as' [] ' elif j.name == '\n': dot.node(str(i), '\\\\n') # The newline character is shown as' \ n 'escape, where it will also be called, so four slashes are required elif j.name == '\t': dot.node(str(i), '\\\\t') # The tab displays as' \ t ' else: dot.node(str(i), j.name) dot.attr('graph', rankdir='LR') for i in self.nodes[::-1]: if not (i.rchild or i.lchild): break if i.lchild: dot.edge(str(self.nodes.index(i)), str( self.nodes.index(i.lchild)), '0', constraint='true') if i.rchild: dot.edge(str(self.nodes.index(i)), str( self.nodes.index(i.rchild)), '1', constraint='true') dot.render(filename, view=False, format='svg', cleanup=True) def checkDecodedText(text: str) -> bool: # Check the legality of ciphertext content for i in text: if i not in ['0', '1', '\n']: return False return True rawTextEdit: QTextEdit = None # Text box where the original text is located encodedTextEdit: QTextEdit = None # Text box of ciphertext HFTree: HuffmanTree = None # Huffman tree CharacterSet = {} # character set showSVGWidget: "ShowSVGWidget" = None # Show svg controls paintTreeWindow:"PaintTreeWindow"=None charsetWindow:"CharsetWindow"=None class MainWindow(QWidget): encodedTextEdit: QTextEdit def __init__(self): super().__init__() loadUi("ui/main.ui", self) # Each control in the form is loaded self.setWindowIcon(QIcon("ui/icon.ico")) global rawTextEdit rawTextEdit = self.rawTextEdit global encodedTextEdit encodedTextEdit = self.encodedTextEdit self.rawSaveFileButton.clicked.connect(self.saveRawTextContent) self.encodedSaveFileButton.clicked.connect(self.saveEncodedTextContent) self.rawOpenFileButton.clicked.connect(self.encodeFileReadin) self.encodedOpenFileButton.clicked.connect(self.decodeFileReadin) self.pushButton.clicked.connect(lambda: QMessageBox.about( self, "About the author", "Name: Sun Lianqi\n School: Zhejiang University of Technology\n Date: 2021.6")) self.compactFormatButton.clicked.connect(self.compactFormPrint) self.encodedButton.clicked.connect(self.encoding) self.decodeButton.clicked.connect(self.decoding) def saveRawTextContent(self): filePath, ok = QFileDialog.getSaveFileName(self, 'Select file') if ok: with open(filePath, 'w', encoding='utf-8') as file: file.write(self.rawTextEdit.toPlainText()) def saveEncodedTextContent(self): if not checkDecodedText(self.encodedTextEdit.toPlainText()): QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return filePath, ok = QFileDialog.getSaveFileName(self, 'Select file') if ok: with open(filePath, 'w', encoding='utf-8') as file: file.write(self.encodedTextEdit.toPlainText()) def encodeFileReadin(self): filePath, ok = QFileDialog.getOpenFileName(self, 'Select file') if ok: with open(filePath, 'r', encoding='utf-8') as file: try: text = file.read() except UnicodeDecodeError: QMessageBox.critical( self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok) return self.rawTextEdit.setText(text) def decodeFileReadin(self): filePath, ok = QFileDialog.getOpenFileName(self, 'Select file') if ok: with open(filePath, 'r', encoding='utf-8') as file: try: encodedTextEdit = file.read() except UnicodeDecodeError: QMessageBox.critical( self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok) return if not checkDecodedText(encodedTextEdit): QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return self.encodedTextEdit.setText(encodedTextEdit) def compactFormPrint(self): Text = self.encodedTextEdit.toPlainText() text = '' m = 50 for i in Text.replace('\n', ''): text += i m -= 1 if m == 0: text += '\n' m = 50 self.encodedTextEdit.setPlainText(text) def encoding(self): if not HFTree: QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok) elif rawTextEdit.toPlainText() == '': QMessageBox.critical(self, "error", "Please enter the original text", QMessageBox.Ok) else: t = HFTree.encode(rawTextEdit.toPlainText()) if not t: QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return self.encodedTextEdit.setText(t) def decoding(self): if not HFTree: QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok) elif self.encodedTextEdit.toPlainText() == '': QMessageBox.critical(self, "error", "Please enter the ciphertext", QMessageBox.Ok) else: t = HFTree.decode(self.encodedTextEdit.toPlainText()) if not t: QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return self.rawTextEdit.setText(t) def closeEvent(self, a0: QtGui.QCloseEvent) -> None: try: os.remove("tmp.svg") except FileNotFoundError: pass sys.exit() class DoubleDelegate(QItemDelegate): # Limit the number of floating-point types to def createEditor(self, parent: QWidget, option: 'QStyleOptionViewItem', index: QModelIndex) -> QWidget: edit = QLineEdit(parent) v = QDoubleValidator(parent) v.setBottom(0) edit.setValidator(v) return edit class OneCharDelegate(QItemDelegate): # Limit input to a single character def createEditor(self, parent: QWidget, option: 'QStyleOptionViewItem', index: QModelIndex) -> QWidget: edit = QLineEdit(parent) v = QRegExpValidator(parent) v.setRegExp(QRegExp(r'.')) edit.setValidator(v) return edit class CharsetWindow(QWidget): tableWidget: QTableWidget def __init__(self): super().__init__() loadUi("ui/charset.ui", self) self.tableWidget.setItemDelegateForColumn(0, OneCharDelegate(self)) self.tableWidget.setItemDelegateForColumn(1, DoubleDelegate(self)) self.setWindowIcon(QIcon("ui/icon.ico")) self.addButton.clicked.connect(self.add) self.findButton.clicked.connect(self.find) self.deleteButton.clicked.connect( lambda: self.tableWidget.removeRow(self.tableWidget.currentRow())) self.inputWordFrequencyButton.clicked.connect(self.importWordFrequency) self.saveButton.clicked.connect(self.saveWordFrequency) self.generateButton.clicked.connect( self.generateCharacterSetFromRawtext) # Limit the input of lineedit to one character self.wordFrequencyEdit.setValidator( QRegExpValidator(QRegExp(r'.'), self)) d = QDoubleValidator(self) d.setBottom(0) self.frequencyEdit.setValidator(d) def showEvent(self, e): if HFTree: # If there is a tree, the character set is generated from the existing tree self.tableWidget.clearContents() self.tableWidget.setRowCount(0) global CharacterSet for i, j in CharacterSet.items(): self.add() item1 = QTableWidgetItem(i) item2 = QTableWidgetItem(str(j)) self.tableWidget.setItem( self.tableWidget.rowCount()-1, 0, item1) self.tableWidget.setItem( self.tableWidget.rowCount()-1, 1, item2) def add(self): # Add a blank line self.tableWidget.insertRow(self.tableWidget.rowCount()) def find(self): # Find characters or word frequencies or characters and word frequencies a: str = self.wordFrequencyEdit.text() b: str = self.frequencyEdit.text() i: int = 0 if a and b: while i < self.tableWidget.rowCount(): if self.tableWidget.item(i, 0).text() == a and self.tableWidget.item(i, 1).text() == b: self.resultLabel.setText(str(i+1)) break i += 1 elif not a and b: while i < self.tableWidget.rowCount(): if self.tableWidget.item(i, 1).text() == b: self.resultLabel.setText(str(i+1)) break i += 1 elif a and not b: while i < self.tableWidget.rowCount(): if self.tableWidget.item(i, 0) and self.tableWidget.item(i, 0).text() == a: self.resultLabel.setText(str(i+1)) break i += 1 if i == self.tableWidget.rowCount(): self.resultLabel.setText("not found") def importWordFrequency(self): # Import word frequency filePath, ok = QFileDialog.getOpenFileName(self, 'Select file') if ok: self.tableWidget.clearContents() self.tableWidget.setRowCount(0) with open(filePath, 'r', encoding='utf-8') as file: try: frequency = file.read() except UnicodeDecodeError: QMessageBox.critical( self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.OK) return global CharacterSet CharacterSet = {} textlines = re.findall(r'([\s\S])\t(\S+)(\n|$)', frequency) if len(textlines) == 0: QMessageBox.critical(self, "error", "Character set generation failed", QMessageBox.Ok) return for i, j, _ in textlines: try: CharacterSet[i] = float(j) except ValueError: QMessageBox.critical( self, "error", "Character set generation failed", QMessageBox.Ok) self.tableWidget.clearContents() self.tableWidget.setRowCount(0) CharacterSet = {} return self.add() item1 = QTableWidgetItem(i) item2 = QTableWidgetItem(j) self.tableWidget.setItem( self.tableWidget.rowCount()-1, 0, item1) self.tableWidget.setItem( self.tableWidget.rowCount()-1, 1, item2) def saveWordFrequency(self): # Save file filePath, ok = QFileDialog.getSaveFileName(self, 'Select file') if ok: with open(filePath, 'w', encoding='utf-8') as file: for i in range(self.tableWidget.rowCount()): m = '\t'.join([self.tableWidget.item( i, 0).text(), self.tableWidget.item(i, 1).text()]) file.write(m+'\n') def generateCharacterSetFromRawtext(self): # Generate character set from original text self.tableWidget.clearContents() self.tableWidget.setRowCount(0) def getFrequency(text: str) -> dict: # Word frequency (Statistics) cnt = {} for i in text: if i not in cnt: cnt[i] = 1 else: cnt[i] += 1 return cnt CharacterSet = getFrequency(rawTextEdit.toPlainText()) for i, j in CharacterSet.items(): self.add() item1 = QTableWidgetItem(i) item2 = QTableWidgetItem(str(j)) self.tableWidget.setItem(self.tableWidget.rowCount()-1, 0, item1) self.tableWidget.setItem(self.tableWidget.rowCount()-1, 1, item2) def closeEvent(self, event): # Closing Windows if self.tableWidget.rowCount() == 0: return global CharacterSet CharacterSet = {} # Save the character set in the table into the variable CharacterSet for i in range(self.tableWidget.rowCount()): if self.tableWidget.item(i, 0) and self.tableWidget.item(i, 1): try: CharacterSet[self.tableWidget.item(i, 0).text()] = float( self.tableWidget.item(i, 1).text()) except: pass global HFTree # Update the tree based on the existing character set if CharacterSet != {}: HFTree = HuffmanTree(CharacterSet) global showSVGWidget if showSVGWidget: HFTree.printTree('tmp') showSVGWidget.update() paintTreeWindow.printInform() class NetTransportWindow(QWidget): s: socket.socket = None lineEdit: QLineEdit # Server port input box connectIpEditText: QLineEdit # Client IP input box connectPortEditText: QLineEdit # Client port input box setEncodedTextSign = pyqtSignal(str) # Modified ciphertext box signal def __init__(self): super().__init__() loadUi("ui/network.ui", self) self.setWindowIcon(QIcon("ui/icon.ico")) # View native IP self.showIpButton.clicked.connect(lambda: QMessageBox.information(self, 'View native IP', socket.gethostbyname( socket.gethostname()), QMessageBox.Ok)) self.startServerButton.clicked.connect(self.buildServerConnection) self.connectButton.clicked.connect(self.buildClientConnection) self.sendTreeButton.clicked.connect(self.sendTree) self.sendTextButton.clicked.connect(self.sendText) self.setEncodedTextSign.connect(self.setEncodedText) self.breakButton.clicked.connect(self.breakConnection) # Server side input restrictions self.lineEdit.setValidator(QRegExpValidator(QRegExp( r'((6553[0-5])|[655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[0-9])'), self)) # Client port number input limit self.connectPortEditText.setValidator(QRegExpValidator(QRegExp( r'((6553[0-5])|[655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[0-9])'), self)) # Client ip address input restrictions self.connectIpEditText.setValidator(QRegExpValidator(QRegExp( r'((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])[\\.]){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])'), self)) def buildServerConnection(self): # Server listening port try: # Get port number port = int(self.lineEdit.text()) except ValueError: QMessageBox.critical(self, "error", "There is currently no port number entered", QMessageBox.Ok) return # Create a socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: # Set the listening port and listen s.bind(("0.0.0.0", port)) s.listen() except OSError: QMessageBox.critical(self, "error", "The port is already occupied", QMessageBox.Ok) return # Waiting for client connection self.stateLabel.setText("Waiting for connection") # Start a new thread to wait for the connection to prevent the program from blocking, and use the daemon flag to automatically end all threads with this flag when the main thread ends threading.Thread(target=self.handleClient, args=[s], daemon=True).start() def handleClient(self, s: socket.socket): # Server side waiting for connection c = s.accept()[0] self.stateLabel.setText("Connected") self.s = c # Start the thread waiting to receive threading.Thread(target=self.waitRecv, args=[c], daemon=True).start() def buildClientConnection(self): # Client establish connection try: # Get IP address ip = self.connectIpEditText.text() if ip == None: QMessageBox.critical( self, "error", "There are currently no entered IP address", QMessageBox.Ok) return port = int(self.connectPortEditText.text()) except ValueError: QMessageBox.critical(self, "error", "There is currently no port number entered", QMessageBox.Ok) return s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((ip, port)) except ConnectionRefusedError: QMessageBox.critical(self, "error", "connection failed", QMessageBox.Ok) return except OSError: QMessageBox.critical(self, "error", "IP Or port error", QMessageBox.Ok) return self.stateLabel.setText("Connected") self.s = s # The connection is successful. Start the thread waiting to receive threading.Thread(target=self.waitRecv, args=[s], daemon=True).start() def sendTree(self): # Send tree if not self.s: QMessageBox.critical(self, "error", "Please establish a connection first", QMessageBox.Ok) return global CharacterSet if not CharacterSet or not HFTree: QMessageBox.critical(self, "error", "The current tree is empty", QMessageBox.Ok) return content = 't' # Flag of sending tree for i, j in CharacterSet.items(): content += i+"\t"+str(j)+'\n' # Convert it to Byte for sending self.s.sendall(content.encode()) QMessageBox.information(self, "Tips", "Sent successfully", QMessageBox.Ok) def sendText(self): # Send ciphertext if not self.s: QMessageBox.critical(self, "error", "Please establish a connection first", QMessageBox.Ok) return global encodedTextEdit content = encodedTextEdit.toPlainText() if not checkDecodedText(content): QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok) return self.s.sendall(('c'+content).encode()) QMessageBox.information(self, "Tips", "Sent successfully", QMessageBox.Ok) def setEncodedText(self, text): # Enter the received ciphertext into the text box global encodedTextEdit encodedTextEdit.setText(text) def waitRecv(self, s: socket.socket): # Waiting to accept thread try: while True: data = s.recv(10000000) # Convert content to str type data = data.decode() if data[0] == 't': data = data[1:] textlines = re.findall(r'([\s\S])\t(\S+)(\n|$)', data) global CharacterSet CharacterSet = {} for i, j, _ in textlines: try: CharacterSet[i] = float(j) except ValueError: self.stateLabel.setText("Useless data received") self.tableWidget.clearContents() self.tableWidget.setRowCount(0) CharacterSet = {} return global HFTree if CharacterSet != {}: HFTree = HuffmanTree(CharacterSet) self.stateLabel.setText("Tree received") else: self.stateLabel.setText("Empty tree received") elif data[0] == 'c': self.stateLabel.setText("Ciphertext received") data = data[1:] self.setEncodedTextSign.emit(data) else: self.stateLabel.setText("Useless data received") except ConnectionResetError: # The other party disconnected self.stateLabel.setText("Disconnected") self.s = None except ConnectionAbortedError: # Disconnect yourself pass def breakConnection(self): # Disconnect button event try: self.s.close() self.s = None self.stateLabel.setText("Not connected") except: pass class PaintTreeWindow(QWidget): # Form showing tree height: int = 0 CharacterSet = {} paintingLayout: QVBoxLayout def __init__(self): super().__init__() loadUi("ui/tree.ui", self) self.setWindowIcon(QIcon("ui/icon.ico")) self.findCodeButton.clicked.connect(charsetWindow.show) self.saveButton.clicked.connect(self.savetree) self.loadButton.clicked.connect(self.importtree) global showSVGWidget showSVGWidget = ShowSVGWidget(self) self.paintingLayout.addWidget(showSVGWidget) def showEvent(self, e): # Refresh the picture of the tree on open if not HFTree: QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok) def TreeDepth(self, pRoot: HuffmanTree): # Calculate the depth of the tree def currentTreeDepth(pRoot: TreeNode): if pRoot is None: return 0 if pRoot.lchild or pRoot.rchild: return max(currentTreeDepth(pRoot.lchild), currentTreeDepth(pRoot.rchild))+1 else: return 1 return currentTreeDepth(pRoot.rootnode) def printInform(self): # Update tree information self.treeHeightlabel.setText(str(self.TreeDepth(HFTree))) self.nodeCountlabel.setText(str(len(HFTree.characterset)*2-1)) self.leafCountlabel.setText(str(len(HFTree.characterset))) def importtree(self): # Import the tree information into the picture filePath, ok = QFileDialog.getOpenFileName(self, 'Select file') if ok: with open(filePath, 'r', encoding='utf-8') as file: try: text = file.read() except UnicodeDecodeError: QMessageBox.critical( self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok) return global CharacterSet CharacterSet = self.CharacterSet textlines = re.findall(r'([\s\S])\t(\S+)\t\S+(\n|$)', text) # Reset the character set information after import and update the tree in memory for i, j, _ in textlines: CharacterSet[i] = float(j) global HFTree if CharacterSet != {}: HFTree = HuffmanTree(CharacterSet) global showSVGWidget HFTree.printTree('tmp') showSVGWidget.update() self.printInform() # Write the tree information on the panel def savetree(self): # Save tree information filePath, ok = QFileDialog.getSaveFileName(self, 'Select file') if ok: with open(filePath, 'w', encoding='utf-8') as file: for i, j in HFTree.characterset.items(): m = '\t'.join([i, str(j), HFTree.encode(i)]) file.write(m+'\n') class ShowSVGWidget(QWidget): # Custom control to display svg pictures leftClick: bool svgrender: QSvgRenderer defaultSize: QSizeF point: QPoint scale = 1 def __init__(self, parent=None): super().__init__(parent) self.parent = parent # Construct a blank svg image self.svgrender = QSvgRenderer( b'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 0 0" width="512pt" height="512pt"></svg>') # Get picture default size self.defaultSize = QSizeF(self.svgrender.defaultSize()) self.point = QPoint(0, 0) self.scale = 1 def update(self): # Update picture self.svgrender = QSvgRenderer("tmp.svg") self.defaultSize = QSizeF(self.svgrender.defaultSize()) self.point = QPoint(0, 0) self.scale = 1 self.repaint() def paintEvent(self, a0: QtGui.QPaintEvent) -> None: # Painting event (callback function) painter = QPainter() # paint brush painter.begin(self) self.svgrender.render(painter, QRectF( self.point, self.defaultSize*self.scale)) # svg renderer to paint, (brush, qrectf (position, size)) (F means float) painter.end() def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: # Mouse movement event (callback function) if self.leftClick: self.endPos = a0.pos()-self.startPos self.point += self.endPos self.startPos = a0.pos() self.repaint() def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: # Mouse click event (callback function) if a0.button() == Qt.LeftButton: self.leftClick = True self.startPos = a0.pos() def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None: # Mouse release event (callback function) if a0.button() == Qt.LeftButton: self.leftClick = False def wheelEvent(self, a0: QtGui.QWheelEvent) -> None: # Zoom the image according to the position of the cursor oldScale = self.scale if a0.angleDelta().y() > 0: # enlarge if self.scale <= 5.0: self.scale *= 1.1 elif a0.angleDelta().y() < 0: # narrow if self.scale >= 0.2: self.scale *= 0.9 self.point = a0.pos()-(self.scale/oldScale*(a0.pos()-self.point)) self.repaint() # if __name__ == '__main__': multiprocessing.freeze_support() app = QApplication(sys.argv) mainWindow = MainWindow() charsetWindow = CharsetWindow() nettansportWindow = NetTransportWindow() paintTreeWindow = PaintTreeWindow() mainWindow.show() mainWindow.editFrequencyButton.clicked.connect(charsetWindow.show) mainWindow.networkTransportButton.clicked.connect(nettansportWindow.show) mainWindow.paintTreeButton.clicked.connect(paintTreeWindow.show) sys.exit(app.exec_())
VII Debugging analysis
- Initialization
There are two forms of initialization: one is generated directly from the original text, and the other is based on importing characters directly
- Encoding
- Decoding
- Print code file (print)
- Print Huffman Tree printing
- Network communication
Disadvantages:
- The input of ciphertext cannot be restricted (because textedit has extremely rich functions, such as pasting with format, it cannot restrict the format of ciphertext, and can only judge the content during transmission, reading and writing)
- After the server is disconnected from the client, the server cannot wait for the next reconnection
- The length of each reception shall not exceed 10000000
- Due to the precision of floating point numbers, 6.0 and 6 may not be the same
- The process of saving pictures is complicated
advantage:
- Regular expressions are used to restrict the input of characters, word frequency, port number and ip address
- Special judgment on various illegal operations
- Zoom the image according to the position of the cursor, and draw with a vector diagram without distortion when zooming in and out
8, Experimental experience (summary)
Through the study of this data structure course design, I have a deeper understanding of the algorithms in the data structure, especially the construction of Huffman tree and the coding and decoding of Huffman encoder. There are many bugs in writing code, but after continuous debugging, the previously incomprehensible code is more familiar. When there is a bug in your program, you should first check whether there are problems in some small details of your program, and find the error and improve it through the error reporting of the compiler. If you can't find the problem, you need to borrow online materials and help yourself complete the debugging of the program through their experience.
Through this course design, I have enhanced my ability to design programs independently and debug programs, which has benefited me a lot.