Как записать уже сгенерированные коды Хаффмана в файл в Rust

Я пытаюсь реализовать кодировку Хаффмана для сжатия файлов (в целях обучения). Я могу сгенерировать коды Хаффмана для данного сообщения и использовать их для построения двоичной строки, содержащей сжатое сообщение. Однако я не понимаю, как сохранить эти «сжатые» данные. Я хочу, чтобы эти данные были как можно более компактными (поскольку они сжимаются), но я не уверен, что serde — правильный подход. Даже если это то, как мне сериализовать? (Например, что-то вроде Json, вероятно, не подойдет для моего варианта использования, поскольку меня не волнует читабельность для человека). Возможно, мне стоит просто записать байты в файл, но тогда я не знаю, как включить словарь поиска.

use bitvec::prelude::*;
use itertools::Itertools;
use std::{collections::HashMap, fmt, fs::File, io::{BufWriter, Read}, path::Path};
use serde::{Deserialize, Serialize, Serializer}

#[derive(Serialize, Deserialize, Debug)]
struct HuffmanData {
    dictionary: HashMap<String, char>,
    message: BitVec,
}

fn main() {
    let path = Path::new("data").join("romeo-juliet.txt") ;
    let file = File::open(path).expect("Could not open file");
    let huffman_data: HuffmanData = huffman(file);
    let save_path = Path::new("./compressed-data/").join("romeo-juliet.txt");
    let save_file = File::create(save_path).unwrap();
}

Я пропустил определения функций, поскольку они не нужны и работают по назначению.

Не сохраняйте коды, вместо этого сохраните счетчики для каждого байта, которые вы использовали для генерации кодов: это 256 значений, которые вы можете либо выгрузить как есть (1 КБ, если ваши счетчики 32 бита), либо сжать счетчики, используя простую переменную: схема длины без параметров (например: для каждого счетчика, если счетчик < 255, сохраните счетчик как один u8, иначе сохраните 0xFF и повторите попытку с count - 255)

Jmb 09.08.2024 14:45
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
1
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Там куча вопросов.

Код Хаффмана для символа имеет длину, измеряемую в битах. Когда вы записываете файл, вы записываете его в байтах, где каждый байт состоит из восьми бит. Так как же соединить эти два? Вам нужно рассматривать поток байтов в файле как поток, в восемь раз превышающий количество битов. Вам нужно будет решить, является ли первый бит вашего битового потока старшим битом первого байта или наименее значимым битом первого байта. Давайте выберем самое значимое.

В вашем коде вы используете целое число без знака в качестве битового буфера. Допустим, это 64-битное целое число, u64. У вас есть код Хаффмана, который вы хотите написать, длиной в k бит. Используйте битовые операции или и сдвиг, чтобы поместить эти биты в буфер целочисленных битов, сдвигая уже находящиеся там биты. Также увеличьте счетчик, который отслеживает количество битов в буфере, инициализированном нулем. Так:

    buf = (buf << k) | code;
    bits += k;

где code — целое число без знака с кодом Хаффмана в младших битах k и нулями в битах выше него.

Если вы продолжите это делать, вы в конечном итоге потеряете биты верхней части буфера. Здесь мы сопоставляем поток байтов, который представляет собой файл. Всякий раз, когда количество бит в буфере равно восьми и более, нам достаточно, чтобы записать байт. Так что продолжайте записывать байты, оставляя незаписанными не более семи битов в буфере:

    while bits >= 8 {
        bits -= 8;
        put(buf >> bits);
    }

где put() записывает один байт в ваш вывод. Делайте это каждый раз, когда помещаете биты в битовый буфер.

64-битный буфер допускает использование до 57-битных кодов. Это должно охватывать любое кодирование, которое вы делаете, поскольку для генерации 58-битного кода вам потребуется, чтобы общее количество частот составляло не менее 2 139 295 485 798. Коды Хаффмана должны генерироваться одновременно на фрагментах данных, чтобы коды могли адаптироваться к изменениям в статистике данных.

Когда вы закончите, обязательно выпишите все оставшиеся биты в буфере в верхнюю часть последнего байта, который может оказаться с некоторыми битами заполнения нулями внизу. (Подробнее о том, как декодер должен обрабатывать эти биты заполнения, ниже.)

    // flush
    if bits != 0 {
        put(buf << (8 - bits));
        bits = 0;
    }

Как повернуть это вспять, чтобы прочитать байты из файла и использовать их в виде потока битов, остается в качестве упражнения для читателя.

Теперь другой вопрос. Если вы просто запишете в файл коды Хаффмана, получатель файла не будет знать, как интерпретировать эти биты. Им необходимо описание кода Хаффмана перед данными, закодированными Хаффманом. Так как же это записывается на выходе?

Существует множество способов кодирования кода Хаффмана в выходные данные. Возможно, самый простой и относительно компактный способ — пройти рекурсивно сверху по дереву, которое вы построили, записывая бит 0 для каждой встреченной ветви или бит 1, за которым следуют биты, описывающие символ на этом листе (например, восемь битов). если вы Хаффман, кодирующий серию байтов). Это самозавершающееся описание, и поэтому за ним сразу же могут следовать данные, закодированные Хаффманом. Вы можете использовать описанный выше подход для записи этого потока битов на выход.

Меньшее представление можно получить, уделив немного больше внимания самому коду Хаффмана, используя «канонический» код Хаффмана. Для этого кода вы сохраняете только то количество битов, которое необходимо для кода для каждого символа. Затем вы выбрасываете дерево. Коды Хаффмана затем присваиваются символам в порядке от самых коротких кодов к самым длинным кодам, а затем внутри каждой длины путем лексикографического упорядочения символов. Тогда вам нужно будет отправить получателю только эти длины, а не описание всего дерева. Пример такой схемы вы можете найти в описании формата сжатых данных deflate.

Наконец, вам нужно быть осторожным с форматом сжатых данных и этими битами заполнения нулями 0–7 в конце последнего байта. Как получатель узнает, когда битовый поток закончится? Эти последние несколько нулевых битов с таким же успехом можно интерпретировать как один или несколько символов, если в вашем коде Хаффмана есть несколько коротких кодов по несколько битов каждый. Чтобы предотвратить это, вы можете либо добавить к своим данным количество символов, которые необходимо декодировать, либо добавить к тому, что вы кодируете, символ «конца потока», имеющий частоту, равную единице. Завершите трансляцию кодом этого символа. Тогда декодер будет знать, что нужно остановиться, когда получит этот код.

Другие вопросы по теме