последующей оценкой результатов - разрабатываемый тестирующий клиент может быть использован не только на соревнованиях по программированию, но и, например, для организации сетевого сервиса по удаленному исполнению кода.
Литература
1. Боженкова Е.Н., Иртегов Д.В., Киров А.В., Нестеренко Т.В., Чурина Т.Г. Автоматизированная система тестирования NSUts: Требования и разработка прототипа // Вестник НГУ, серия: Информационные технологии. -N4, Т.8, 2010. - С. 46-53.
2. Киров А.В. Изолирующая среда для запуска тестовых прикладных программ // Материалы VII Всероссийской научно-практической конференции студентов, аспирантов и молодых ученых «Студент и современные информационные технологии». - Томск: Томский политехн. ун-т. 2009. - С. 285-286.
3. David Maynor. Metasploit Toolkit for Penetration Testing, Exploit Development, and Vulnerability Research - Syngress, 2007 - 350 p.
4. Greg Hoglund, Jamie Butler. Rootkits: Subverting the Windows Kernel - 1st Edition - Addison Wesley, 2005 - 352 p.
5. Официальный сайт виртуализирующего ПО Oracle VirtualBox. [Электронный ресурс]. URL: http://virtualbox.org/
6. Joe Johnston, Edd Dumbill, Simon St.Laurent, John Posner. Programming Web Services with XML-RPC - 1st Edition - O’Reilly Media, 2001 - 230 p.
7. Официальный сайт интерпретатора Stackless Python. [Электронный ресурс]. URL: http://www.stackless.com/
УДК 519.682.4+ 371.32
ОБУЧЕНИЕ СТАНДАРТНОЙ БИБЛИОТЕКЕ ШАБЛОНОВ STL ЯЗЫКА С++
НА ПРИМЕРЕ РАЗРАБОТКИ КОМПРЕССОРА ПО МЕТОДУ ХАФФМАНА
Штанюк Антон Александрович, к.т.н., доцент, Нижегородский коммерческий институт, Россия,
Нижний Новгород, [email protected]
Стандартная библиотека шаблонов языка C++ является мощным средством обобщённого и объектно-ориентированного программирования, значительно упрощающим процесс разработки крупных программных систем. Обучение данному средству очень важно в современных курсах подготовки специалистов в области информационных систем. Однако часто преподаватели испытывают недостаток в реальных примерах, на которых бы иллюстрировалось использование элементов STL. В рамках настоящей работы предлагается такой пример, имеющий практическое значение: программа-компрессор, сжимающая отдельные файлы.
Компрессор по методу Хаффмана [1] относится к классу программ, сжимающих файлы за счёт уменьшения избыточности, вызванной использованием равномерного ASCII-кодирования при наличии разных вероятностей появления элементов алфавита. Несмотря на то, что строковое сжатие более эффективно в практическом отношении, символьное сжатие хорошо иллюстрирует идеи теории информации, а также требует использования множества конструкций языка программирования при реализации, позволяя достичь целей обучения. В настоящей работе речь пойдёт об использовании стандартной библиотеки шаблонов при разработке компрессора. Данный пример хорошо подходит для иллюстрации возможностей объектного подхода при решении задач с привлечением контейнеров и алгоритмов STL [2] и может быть рекомендован в качестве учебного примера в рамках курсов по объектноориентированному программированию на С++.
Компрессор принимает на вход обрабатываемый файл (1) и анализирует его. Результатом анализа выступает частотная таблица символов (байт) исходного файла (2). На основе этой таблицы генератор кода строит бинарное дерево Хафманна, необходимое для получения префиксного кода (3), который наилучшим образом подходит для представления
106
информации в файле. Кодер читает входной файл снова и перекодирует его в соответствии с новым префиксным неравномерным кодом (4), выдавая закодированное представление в виде текстовой строки из нулей и единиц. Закодированное содержимое подвергается упаковке (осуществляется перевод из строкового представления в битовое) и помещается в результирующий (сжатый) файл вместе с заголовком, содержащим служебную информацию.
Рис. 1 - Схема компрессора
Опишем кратко те части программы, в которых используются элементы STL. Основной структурой для хранения информации об одном элементе ASCII-алфавита является структура SYM:
struct SYM
{ unsigned char ascii; float freq; string code;
SYM *left;
SYM *right;
};
// ASCII-код (исходный) символа // частота встречаемости в исходном файле // новый код по Хаффману // для построения дерева // для построения дерева
Структура содержит два указателя на SYM, позволяющих связывать объекты друг с другом при построении бинарного дерева.
Для хранения информации обо всех символах алфавита мы применяем контейнер vector из STL. Для доступа ко всем элементам вектора создаётся соответствующий итератор
vector<SYM*> psym;
vector<SYM*>::iterator psymIter;
Для того, чтобы сформировать вектор, нам понадобится вспомогательный словарь symStat, являющийся контейнером STL map
map<unsigned char, long> symStat;
map<unsigned char, long>::iterator symIter;
Словарь symStat мы используем для сбора статистики встречаемости символов исходного файла. Он представляет собой пары «символ - счётчик» и легко заполняется при движении по файлу. После сканирования исходного файла мы имеем информацию о количествах всех символов и на её основе формируем вектор указателей psym.
Каждый элемент вектора psym представляет собой адрес структуры SYM, описывающий один уникальный символ исходного файла. Рассмотрим алгоритм построения бинарного дерева Хаффмана для файла, содержащего последовательность символов:
zzxz.yzyxxzxy.yzxxxzy.yx.
zxyyxzyx.xyzyxxzzyyz.xzxy
.xxzxyxxzxyyz.yxxzxyyzyxx
zx.zzxyyyxzxxxyxx.zxxxzxx
Слева приведены записи о символах исходного файла, представленные в виде массива и отсортированные в порядке убывания частоты. На основе двух символов с наименьшей частотой мы создаём узлы дерева и снова включаем их в массив. Процесс рекурсивно повторяется до тех пор, пока в массиве не окажется единственный элемент, выступающий в роли корня дерева.
Рассмотрим реализацию функции построения дерева. В ней используется контейнер vector, содержащий указатели на узлы (SYM), а также вызывается алгоритм sort для сохранения порядка следования элементов в векторе. Для обработки содержимого вектора
107
вызываются методы push_back() и pop_back() для помещения и удаления элементов, соответственно.
Рис. 2 - Схема построения дерева Хаффмана
bool compare (SYM *a,SYM *b) { return (a->freq>b->freq); }
SYM *makeHuffTree(vector<SYM*> &psym)
{ // создаём новый элемент
SYM *temp = new SYM;
temp->freq = psym[psym.size()-1]->freq+psym[psym.size()-2]->freq;
// образуем связи для построения дерева temp->left = psym[psym.size()-1]; temp->right = psym[psym.size()-2];
// дерево построено, если в массиве остались 2 элемента if(psym.size()== 2) return temp;
// помещаем новый элемент в массив psym.push back(temp);
// сортируем массив по убыванию частот
sort(psym.begin(),psym.end(),compare);
// удаляем из массива 2 последних элемента psym.pop back(); psym.pop back();
// рекурсивно вызываем себя для продолжения построения дерева return makeHuffTree(psym);
}
На следующем этапе мы выполняем рекурсивный обход дерева, с добавлением в поле code каждого узла 1 и 0, в зависимости от перехода в левое или в правое поддерево.
Рис. 3 - Дерево Хаффмана и схема кодирования
108
Для ускорения процесса кодирования исходного файла создаётся специальный словарь symCodes с исходными символами и кодовыми комбинациями на основе рекурсивного обхода построенного дерева
void makeCodesMap(SYM *root, map<unsigned char,string>& symCodes)
{
if(root->left)
makeCodesMap(root->left,symCodes);
if(!root->left && !root->right)
symCodes[root->ascii]=root->code;
if(root->right)
makeCodesMap(root->right,symCodes);
}
На этапе кодирования происходит формирование строкового потока с закодированным представлением содержимого исходного файла. Для каждого символа из обрабатываемого файла определяется кодовая комбинация из словаря, а затем эта комбинация поступает в строковый поток.
// строковый поток для кодовой последовательности stringstream data101 (stringstream::in | stringstream::out);
// цикл чтения входного файла (in) для кодирования методом Хаффмана while(1) {
ch=in.get(); if(ch==-1) break; else {
// накапливаем закодированные данные data101<<symCodes[(unsigned char)ch]; codelen+=symCodes[(unsigned char)ch].length();
}
}
Как только закончено формирование закодированного представления исходного файла, необходимо произвести его сжатие. Для решения этой задачи используется контейнер bitset, который заполняется на основе строки из восьми нулей и единиц. Благодаря специальному методу to_ulong() удаётся получить сжатое представление данных и вывести его в результирующий файл вместе с заголовком.
while(!data101.eof()) { data101.read(buf,8); string temp=buf; bitset <8> eightbits (temp);
out.put((unsigned char)eightbits.to ulong()); memset(buf,0,8);
}
Таким образом, данный учебный пример показывает применение таких элементов STL, как контейнеры vector, bitset, map, итераторов для доступа к элементам контейнеров, алгоритма sort и строковых, файловых и стандартных потоков ввода/вывода.
Литература
1. Д. Сэломон. Сжатие данных, изображений и звука. - М: Техносфера, 2006.
2. Лишнер Р. STL. Карманный справочник. - СПб.: Питер, 2005.
109