From 5125930a50eda2074594a5849535d829e71c6606 Mon Sep 17 00:00:00 2001 From: helldh Date: Wed, 21 Jan 2026 09:29:19 +0300 Subject: [PATCH] Add comments in source file --- huffman.py | 111 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 11 deletions(-) diff --git a/huffman.py b/huffman.py index 13714ee..d7dc77b 100644 --- a/huffman.py +++ b/huffman.py @@ -1,15 +1,46 @@ +# huffman.py +""" +Прототип реализации алгоритма Хаффмана на Python. + +Особенности: +- Статическое хранение частот (массив 256 элементов) +- Дерево Хаффмана хранится списком узлов (HTLS) +- Кодирование и декодирование побитовое +- Совместимо с переносом в C (фиксированные массивы и сдвиги) +""" + from typing import BinaryIO, List, Tuple from dataclasses import dataclass -# structure implementation in Python @dataclass -class HTLS: # short alias of Huffman Tree List Structure +class HTLS: + """ + Структура узла дерева Хаффмана. + + Атрибуты: + left: индекс левого потомка (None, если лист) + right: индекс правого потомка (None, если лист) + value: кортеж (byte_value, frequency) для листа, + (None, frequency) для внутренних узлов + """ left: int | None right: int | None value: Tuple[int | None, int] -# Step 1: Count frequencies -def get_frequencies(fd: BinaryIO): +# ----------------------------------------------------------------------------- +# Шаг 1: подсчёт частот байтов +# ----------------------------------------------------------------------------- +def get_frequencies(fd: BinaryIO) -> List[int]: + """ + Собирает частоты каждого байта в файле. + + Аргументы: + fd: открытый бинарный файл для чтения + + Возвращает: + Список из 256 элементов, где индекс = значение байта, + значение = частота этого байта в файле. + """ frequencies = [0] * 256 content = fd.read() @@ -18,21 +49,36 @@ def get_frequencies(fd: BinaryIO): return frequencies +# ----------------------------------------------------------------------------- +# Шаг 2: построение дерева Хаффмана +# ----------------------------------------------------------------------------- +def make_tree(frequencies_table: List[int]) -> Tuple[List[HTLS], int]: + """ + Строит дерево Хаффмана на основе массива частот. -def make_tree(frequencies_table: List[int]): + Аргументы: + frequencies_table: массив частот 256 байтов + + Возвращает: + nodes: список всех узлов дерева (HTLS) + root_idx: индекс корневого узла + """ + # создаём узлы-«листья» для каждого байта nodes = [HTLS(left=None, right=None, value=(i, freq)) for i, freq in enumerate(frequencies_table)] + # список «живых» узлов для объединения alive = list(range(len(nodes))) while len(alive) > 1: + # находим два узла с наименьшей частотой alive.sort(key=lambda idx: nodes[idx].value[1]) - i1 = alive.pop(0) i2 = alive.pop(0) freq1 = nodes[i1].value[1] freq2 = nodes[i2].value[1] + # создаём новый узел, объединяющий два наименьших new_node = HTLS( left=i1, right=i2, @@ -43,18 +89,34 @@ def make_tree(frequencies_table: List[int]): return nodes, alive[0] -def make_codes(nodes: List[HTLS], root: int): +# ----------------------------------------------------------------------------- +# Шаг 3: генерация кодов Хаффмана +# ----------------------------------------------------------------------------- +def make_codes(nodes: List[HTLS], root: int) -> dict[int, Tuple[int, int]]: + """ + Генерирует побитовые коды для каждого байта по дереву Хаффмана. + + Аргументы: + nodes: список узлов HTLS + root: индекс корня дерева + + Возвращает: + Словарь: ключ = байт (0–255), значение = (код, длина) + Код хранится как int, длина = количество бит. + """ codes = {} - stack = [(root, 0, 0)] + stack = [(root, 0, 0)] # (индекс узла, текущий код, длина кода) while stack: idx, code, length = stack.pop() node = nodes[idx] if node.value[0] is not None: + # достигнут лист byte = node.value[0] codes[byte] = (code, length) else: + # сначала правый потомок (для корректного обхода LIFO) if node.right is not None: stack.append((node.right, (code << 1) | 1, length + 1)) if node.left is not None: @@ -62,7 +124,18 @@ def make_codes(nodes: List[HTLS], root: int): return codes -def encode_flow(input_fd, output_fd, codes): +# ----------------------------------------------------------------------------- +# Шаг 4: кодирование файла +# ----------------------------------------------------------------------------- +def encode_flow(input_fd: BinaryIO, output_fd: BinaryIO, codes: dict[int, Tuple[int, int]]): + """ + Побитово кодирует содержимое input_fd и записывает в output_fd. + + Аргументы: + input_fd: бинарный входной файл + output_fd: бинарный выходной файл + codes: словарь байт -> (код, длина) + """ buffer = 0 buffer_len = 0 @@ -71,20 +144,35 @@ def encode_flow(input_fd, output_fd, codes): for byte in content: code, length = codes[byte] - + # добавляем код в буфер buffer = (buffer << length) | code buffer_len += length + # пока накоплено >= 8 бит → пишем байт while buffer_len >= 8: buffer_len -= 8 to_write = (buffer >> buffer_len) & 0xFF output_fd.write(bytes([to_write])) + # остаток, если есть if buffer_len > 0: to_write = (buffer << (8 - buffer_len)) & 0xFF output_fd.write(bytes([to_write])) -def decode_flow(input_fd, output_fd, nodes, root_idx, total_bytes): +# ----------------------------------------------------------------------------- +# Шаг 5: декодирование файла +# ----------------------------------------------------------------------------- +def decode_flow(input_fd: BinaryIO, output_fd: BinaryIO, nodes: List[HTLS], root_idx: int, total_bytes: int): + """ + Декодирует поток Хаффмана обратно в исходные байты. + + Аргументы: + input_fd: бинарный файл с закодированными данными + output_fd: бинарный выходной файл для декодированных байт + nodes: список узлов дерева Хаффмана + root_idx: индекс корня дерева + total_bytes: общее количество исходных байт (чтобы остановиться) + """ input_fd.seek(0) current_node = root_idx bytes_written = 0 @@ -100,6 +188,7 @@ def decode_flow(input_fd, output_fd, nodes, root_idx, total_bytes): node = nodes[current_node] current_node = node.right if bit else node.left + # если достигнут лист if nodes[current_node].value[0] is not None: output_fd.write(bytes([nodes[current_node].value[0]])) bytes_written += 1