Add comments in source file

This commit is contained in:
helldh 2026-01-21 09:29:19 +03:00
parent 458201e00b
commit 5125930a50

View file

@ -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: индекс корня дерева
Возвращает:
Словарь: ключ = байт (0255), значение = (код, длина)
Код хранится как 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