Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kernigan_B__Payk_R_Praktika_programmirovania.pdf
Скачиваний:
76
Добавлен:
18.03.2016
Размер:
2.53 Mб
Скачать

префикс prefix.push_back(w);

}

}

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

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

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

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

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

AWK и PERL

Чтобы завершить наши упражнения, мы написали программу еще и на двух популярных языках скриптов — Awk и Perl. В них есть возможности, необходимые для нашего приложения, — ассоциативные массивы и методы обработки строк.

Ассоциативный массив (associative array) — это подходящий контейнер для кэштаблицы; он выглядит как простой массив, но его индексами являются произвольные строки, или числа, или наборы таких элементов, разделенных запятыми. Это разновидность отображения данных одного типа в данные другого типа. В Awk все массивы являются ассоциативными; в Perl есть как массивы, индексируемые стандартным образом, целочисленными индексами, так и ассоциативные массивы, которые называются "хэшами" (hashes), — из их названия сразу становится ясен способ их внутренней реализации.

Предлагаемые версии на Awk и Perl рассчитаны только на работу с префиксами длиной в два слова.

# markov.awk: алгоритм цепей Маркова для префиксов из 2 слов

BEGIN { MAXGEN = 10000;

NONWORD = "\n"> w1 = w2 = NONWORD } .

{for (i = 1; i <= NF; i++)

{# читать все словаstatetab [w1,w2,++nsuffix[w1,w2]] = $i wl = w2

w2 = $1 } } END { Statetab[w1,w2,++nsuffix[w1,w2]]

=NONWORD

ft добавить метку конца ввода w1 = w2 = NONWORD

for (i = 0; i < MAXGEN; i++)

{# генерируем

г= int(rand()*nsuffix[w1,w2])

+1 # nsuffix >= 1 p = statetab[w1,w2, r] if (p == NONWORD)

exit print p

w1 = w2 # идти дальше по цепочке w2 =.p

}

)

Awk — язык, функционирующий по принципу "образец-действие": входной поток читается по строке за раз, каждая строка сравнивается с образцом, для каждого совпадения выполняется соответствующее дей-Ствие. Существуют два специальных образца-— BEGIN и END, которые вызываются, соответственно, перед первой строкой ввода и после последней.

Действие — это блок выражений, заключенный в фигурные скобки. В Awk-версии в блоке BEGIN инициализируется префикс и пара других переменных.

Следующий блок не имеет образца, поэтому он по умолчанию вызывается для каждой новой строки ввода. Awk автоматически разделяет каждую вводимую строку на поля (ограниченные пробелами слова), имеющие имена от $1 до $NF; переменная NF — это количество полей. Выражение

Statetab[w1,w2,++nsuffix[w1,w2]] = $i

создает отображение префиксов в суффиксы. Массив nsuffix считает суффиксы, а элемент nsuf f ix[w1, w2] считает количество суффиксов, ассоциированных с префиксом. Сами суффиксы хранятся в элементах массива statetab[w1,w2, 1], statetab[w1, w2, 2] и т. д.

Блок END вызывается на выполнение после тогсц как весь ввод был считан. К этому моменту для каждого префикса существует элемент nsuffix, содержащий количество суффиксов, и, соответственно, существует именно столько элементов statetab, содержащих сами суффиксы.

Версия Perl выглядит похожим образом, но в ней для хранения суффиксов используется безымянный массив, а не третий индекс; для обновления же префикса используется множественное присваивание. В Perl для обозначения типов переменных применяются специальные символы: $ обозначает скаляр, @ — индексированный массив, квадратные скобки [ ] используются для индексации массивов, а фигурные скобки { } — для индексации хэшей.

# markov.pl: алгоритм цепей Маркова для префиксов из 2 слов

SMAXGEN = 1000;

$NONWORD = "\п";

$w1 = $w2 = $NONWORD;

# начальное состояние

while (о) { n читать каждую строку ввода foreach (split)

{

push((s>{$statetab{$w1} {$w2}

}, $_); ($w1, $w2) = ($w2, $_);

it множественное присваивание

}

I-push(@{$statetab{$w1} {$w2}

}, $NONWORD);

# добавить метку конца ввода $w1 = $w2 = $NONWORD;

for ($i =0; $i < $MAXGEN; $i++)

{

$suf = $statetab{$w1} {$w2};

# ссылка на массив $r = int(rand @$suf); n @$suf - количество элементов

exit if (($t = $suf"->[$r]) eq $NONWORD); print "$t\n";

($w1, $w2) = ($w2, $t);

# обновить префикс

}

Как и в предыдущей программе, отображение хранится при помощи переменной statetab. Центральным моментом программы является выражение

push(@{$statetab{$w1}{$w2}}, $_);

которое дописывает новый суффикс в конец (безымянного) массива, хранящегося в statetab{$w1}{$w2). На этапе генерации $statetab{$w1}{$w2} является ссылкой на массив суффиксов, a$suf->[$r] указывает на суффикс, хранящийся под номером r.

Программы и на Awk, и на Perl гораздо короче, чем любая из трех предыдущих версий, но их тяжелее адаптировать для обработки префиксов, состоящих из произвольного количества слов. Ядро программ на С и C++ (функции add и generate) имеет сравнимую длину, но выглядит более понятно. Тем не менее языки скриптов нередко являются хорошим выбором для экспериментального программирования, для создания прототипов и даже для производственного использования в случаях, когда время счета не играет большой роли.

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

Попробуйте преобразовать приложения на Awk и Perl так, чтобы они могли обрабатывать префиксы произвольной длины. Попробуйте определить, как это скажется на быстродействии программ.

Производительность

Теперь можно сравнить несколько вариантов программы. Мы засекали время счета на библейской Книге Псалмов (версия перевода King James Bible), в которой содержится 42 685 слов (5238 уникальных слов, 22 482 префикса). В тексте довольно много повторяющихся фраз ("Blessed is the..."), так что есть списки суффиксов, имеющие более 400 элементов, и несколько сотен цепей с десятками суффиксов, и это хороший набор данных для теста.

Blessed is the man of the net. Turn thee unto me, and raise me up, that

I may tell all my fears. They looked unto him, he heard. My praise shall

be blessed. Wealth and riches shall be saved. Thou hast dealt well with

thy hid treasure: they are cast into a standing water, the flint into

a standing water, and dry ground into watersprings.

Приведенная таблица показывает время в секундах, затрачиваемое на j генерацию 10 000 слов; тестирование проводилось на машине 250 MHz MIPS R10000 с операционной системой Irix 6.4 и на машине Pentium II 400 MHz со 128 мегабайтами памяти под Windows NT. Время выполнeния почти целиком определяется объемом вводимого текста; генерация происходит несравненно быстрее. В таблице приведен также примерный размер программы в строках исходного кода.

 

 

 

 

 

 

 

 

 

250 MHz RlOOOO(c)

 

400 MHz Pentium II (c)

 

Строки исходного

 

 

 

 

кода

 

 

 

 

С

 

0.36

 

0.30

 

150

 

 

 

 

Java

 

4.9

 

9.2

 

105

 

 

 

 

C++/STL/deque

 

2.6

 

11.2

 

70

 

 

 

 

C++/STL/list

 

1.7

 

1.5

 

70

 

 

 

 

Awk

 

2.2

 

2.1

 

20

 

 

 

 

Perl

 

1.8

 

1.0

 

18

 

 

 

 

 

 

 

Версии С и C++ компилировались с включенной оптимизацией, а программы Java запускались с включенным JIT-компилятором (Just-In-Time, "своевременная" компиляция). Время, указанное для версий С и C++ под Irix, — лучшее врейя из трех возможных компиляторов; сходные результаты были получены и для машин Sun SPARC и DEC Alpha.

Как видно из таблицы, версия на языке С лидирует с большим отрывом, следом за ней идет реализация на Perl. He надо забывать, однако, что результаты, указанные в таблице, относятся к нашим экспериментам с определенным набором компиляторов и библиотек, поэтому в своей среде вы можете получить несколько другие характеристики.

Что-то явно не так с версией STL/deque под Windows. Эксперименты показали, что дек, представляющий префиксы, поедает практически все время выполнения, хотя в нем никогда не содержится более двух элементов; казалось бы, большая часть времени должна уходить на основную структуру — отображение. Переход с дека на