Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
kernigan_paik.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.91 Mб
Скачать

Into the air. When water goes into the air it

evaporates. Tie a damp cloth to one end of a solid or

liquid. Look around. What are the solid things?

Chemical changes take place when something burns. If

the burning-material has liquids, they are stable and

the sponge rise. It looked like dough, but it is

burning. Break up the lump of sugar into small pieces

and put them together again in the bottom of a liquid.

Упражнение 3-4

Перепишите Java-версию ma rkov так, чтобы использовать массив вме-I сто класса Vector для префикса в классе Prefix.

3.6. C++

Третий вариант программы мы напишем на C++. Поскольку C++ является почти что расширением С, на нем можно писать как на С (с некоторыми новыми удобствами в обозначениях), а наша изначаль­ная С-версия будет вполне нормальной программой и для C++. Однако при использовании C++ было бы более естественно определить классы для объектов программы — что-то вроде того, что мы сделали на Java — это позволит скрыть некоторые детали реализации. Мы решили пойти даже дальше и использовать библиотеку стандартных шаблонов STL (Standard Template Library), поскольку в ней есть некоторые встроен­ные механизмы, которые могут выполнить значительную часть необхо­димой работы. ISO-стандарт C++ включает в себя STL как часть описа­ния языка.

STL предоставляет такие контейнеры, как векторы, списки и множе­ства, а также ряд основных алгоритмов для поиска, сортировки, добав­ления и удаления элементов данных. Благодаря использованию шабло­нов C++ каждый алгоритм STL работает со всевозможными видами контейнеров, включая как типы, описанные пользователем, так и встро­енные типы данных. Контейнеры реализованы как шаблоны C++, кото­рые инстанцируются для конкретных типов данных; йапример, контей­нер vector может использоваться для создания конкретных типов vector<int> или vector<string>. Все операции, описанные в библиотеке Для vector, включая стандартные алгоритмы сортировки, можно ис­пользовать для таких "производных" типов данных.

В дополнение к контейнеру vector, который схож с Vector в Java, STL предоставляет контейнер deque (дек, гибрид очереди и стека). Дек — это двусторонняя очередь, которая как раз подходит нам для работы с пре­фиксами: в ней содержится NPREF элементов, и мы можем выкидывать первый элемент и добавлять в конец новый, обе операции — за время О(1). Дек STL — более общая структура, чем требуется нам, поскольку она позволяет выкидывать и добавлять элементы с обоих концов, но ха­рактеристики производительности указывают на то, что нам следует ис­пользовать именно ее.

В STL существует также в явном виде и основанный на сбалансиро­ванных деревьях контейнер тар, который хранит пары ключ-значение и осуществляет поиск значения, ассоциированного с любым ключом, зa 0(logn). Отображения, возможно, не столь эффективны, как 0(1) хэш-таблицы, но приятно то, что для их использования не надо писать вообще никакого кода. (Некоторые библиотеки, не входящие в стандарт C++, содержат контейнеры hash или hash_map — они бы подошли просто идеально.)

Кроме всего прочего, мы будем использовать и встроенные функции сравнения, в данном случае они будут сравнивать строки, используя от­дельные строки префикса (в которых мы храним отдельные слова!).

Имея в своем распоряжении все перечисленные компоненты, мы пи­шем код совсем гладко. Вот как выглядят объявления:

typedef deque<string> Prefix;

map<Prefix, vector<string> > statetab; // prefix-> suffixes

Как мы уже говорили, STL предоставляет шаблон дека; запись deque<string> обозначает дек, элементами которого являются строки. Поскольку этот тип встретится в программе несколько раз, мы использо­вали typedef для присвоения ему осмысленного имени Prefix. А вот тип тар, хранящий префиксы и суффиксы, появится лишь единожды, так что мы не стали давать ему уникального имени; объявление тар описывает переменную statetab, которая является отображением префиксов на векторы строк. Это более удобно, чем в С или Java, поскольку нам не потребуется писать хэш-функцию или метод equals.

В основном блоке инициализируется префикс, считывается вводи­мый текст (из стандартного потока ввода, который в библиотеке C++ iostream называется cin), добавляется метка конца ввода и генерируется выходной текст — совсем как в предыдущих версиях:

// markov main: генерация случайного текста

// по алгоритму цепей Маркова

int main(void)

{

int nwords = MAXGEN;

Prefix prefix; // текущий вводимый префикс

for (int i=0; i < NPREF; i++) // начальные префиксы

add(prefix, NONWORD);

build(prefix, cin);

add(prefix, NONWORD);

generate(nwords);

return 0;

}

Функция build использует библиотеку iostream для ввода слов по одному:

// build: читает слова из входного потока,

// создает таблицу состояний

void build(Prefix& prefix, istream& in)

{

string buf;

while (in >> buf)

add(prefix, buf);

}

Строка buf будет расти по мере надобности, чтобы в ней помещались вводимые слова произвольной длины.

В функции add более явно видны преимущества использования STL:

// add: добавляет слово в список суффиксов, обновляет префикс

void add(Prefix& prefix, const strings s)

{

if (prefix.size() == NPREF) {

statetab[prefix].push_back(s);

prefix.pop_front();

}

prefix.push_back(s);

}

Как вы видите, выражения выглядят совсем не сложно; происходящее "за кулисами" тоже вполне доступно пониманию простого смертного. Контейнер тар переопределяет доступ по индексу (операцию [ ] ) для того, чтобы он работал как операция поиска. Выражение statetab[prefix] осуществляет поиск в statetab по ключу prefix и возвращает ссылку на искомое значение; если вектора еще не существует, то создается новый. Функция push_back — такая функция-член класса имеется и в vector, и в deque — добавляет новую строку в конец вектора или дека; pop_f ront удаляет ("выталкивает") первый элемент из дека.

Генерация результирующего текста осуществляется практически так же, как и в предыдущих версиях:

// generate: генерирует вывод - по слову на строку

void generate(int nwords)

{

Prefix prefix;

int i;

for (i = 0; i < NPREF; i++) // начальные префиксы

add(prefix, NONWORD);

for (i = 0; i < nwords; i++) {

vector<string>& suf = statetab[prefix];

const strings w = suf[rand() % suf.size()];

if (w == NONWORD)

break;

cout <<w << "\n";

prefix.pop_front(); // обновляется префикс

prefix.push_back(w);

}

}

Итак, в результате именно эта версия выглядит наиболее понятно и элегантно — код компактен, структура данных ясно видна, а алгоритм абсолютно прозрачен. Но, как и за все хорошее в жизни, за это надо пла­тить — данная версия работает гораздо медленнее, чем версия, написан­ная на С; правда, это все же не самый медленный вариант. Вопросы из­мерения производительности мы вскоре обсудим.

Упражнение 3-5

Одно из главных преимуществ использования STL состоит в той простоте, благодаря которой можно экспериментировать с различны­ми структурами данных. Попробуйте изменить эту версию, используя различные структуры данных для префиксов, списка суффиксов и таб-лицы состояний. Посмотрите, как при этом изменяется производительность.

Упражнение 3-6

Перепишите программу на C++ так, чтобы в ней использовались только классы и тип данных string — без каких-либо дополнительных библиотечных средств. Сравните то, что у вас получится, по стилю кода и скорости работы с нашей STL-версией.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]