- •1. Стиль 10
- •3. Проектирование и реализация 63
- •4. Интерфейсы 85
- •5. Отладка 115
- •6. Тестирование 134
- •7. Производительность 157
- •8. Переносимость 180
- •9. Нотация 203
- •Введение
- •Брайан в. Керниган
- •1.1. Имена
- •1.2. Выражения
- •Упражнение 1 -6
- •1.3. Стилевое единство и идиомы
- •1.4. Макрофункции
- •1.5. Загадочные числа
- •1.6. Комментарии
- •1.7. Стоит ли так беспокоиться?
- •Дополнительная литература
- •2.1. Поиск
- •2.2. Сортировка
- •2.3. Библиотеки
- •2.4. Быстрая сортировка на языке Java
- •2.5. "О большое"
- •2.6. Динамически расширяемые массивы
- •2.7. Списки
- •Упражнение 2-8
- •2.8. Деревья
- •Упражнение 2-15
- •2.10. Заключение
- •Дополнительная литература
- •Проектирование и реализация
- •3.1. Алгоритм цепей Маркова
- •3.2. Варианты структуры данных
- •3.3. Создание структуры данных в языке с
- •3.4. Генерация вывода
- •3.5.Java
- •Into the air. When water goes into the air it
- •3.7. Awk и Perl
- •3.8. Производительность
- •3.9. Уроки
- •Дополнительная литература
- •4. Интерфейсы
- •4.1. Значения, разделенные запятой
- •4.2. Прототип библиотеки
- •4.3. Библиотека для распространения
- •Упражнение 4-4
- •4.5 Принципы интерфейса
- •4.6. Управление ресурсами
- •4.7. Abort, Retry, Fail?
- •4.8. Пользовательские интерфейсы
- •Дополнительная литература
- •5. Отладка
- •5.1. Отладчики
- •5.2. Хорошие подсказки, простые ошибки
- •5.3, Трудные ошибки, нет зацепок
- •5.4. Последняя надежда
- •5.5. Невоспроизводимые ошибки
- •5.6. Средства отладки
- •5.7. Чужие ошибки
- •5.8. Заключение
- •Дополнительная литература
- •6. Тестирование
- •6.1. Тестируйте при написании кода
- •6.2. Систематическое тестирование
- •6.3. Автоматизация тестирования
- •6.4. Тестовые оснастки
- •6.5. Стрессовое тестирование
- •6.6. Полезные советы
- •6.7. Кто осуществляет тестирование?
- •6.8. Тестирование программы markov
- •6.9. Заключение
- •Дополнительная литература
- •7.Производительность
- •7.1. Узкое место
- •7.2. Замеры времени и профилирование
- •7.3. Стратегии ускорения
- •7.4. Настройка кода
- •7.5. Эффективное использование памяти
- •7.6. Предварительная оценка
- •7.7. Заключение
- •Дополнительная литература
- •8. Переносимость
- •8.1. Язык
- •8.2. Заголовочные файлы и библиотеки
- •8.3. Организация программы
- •8.4. Изоляция
- •8.5. Обмен данными
- •8.6. Порядок байтов
- •8.7. Переносимость и внесение усовершенствований
- •8.8. Интернационализация
- •8.9. Заключение
- •Дополнительная литература
- •9.1. Форматирование данных
- •9.2. Регулярные выражения
- •Упражнение 9-12
- •9.3. Программируемые инструменты
- •9.4. Интерпретаторы, компиляторы и виртуальные машины
- •9.5. Программы, которые пишут программы
- •9.6. Использование макросов для генерации кода
- •9.7. Компиляция "налету"
- •Дополнительная литература
- •Интерфейсы
- •Отладка
- •Тестирование
- •Производительность
- •Переносимость
3.7. Awk и Perl
Чтобы завершить наши упражнения, мы написали программу еще и на двух популярных языках скриптов — Awk и Perl. В них есть возможности, необходимые для нашего приложения, — ассоциативные массивы и методы обработки строк.
Ассоциативный массив (associative array) — это подходящий контейнер для хэш-таблицы; он выглядит как простой массив, но его индексами являются произвольные строки, или числа, или наборы таких элементов, разделенных запятыми. Это разновидность отображения данных одного типа в данные другого типа. В Awk все массивы являются ассоциативными; в Perl есть как массивы, индексируемые стандартным образом, целочисленными индексами, так и ассоциативные массивы, которые называются "хэшами" (hashes), — из их названия сразу становится ясен способ их внутренней реализации.
Предлагаемые версии на Awk и Perl рассчитаны только на работу с префиксами длиной в два слова.
# markov.awk: алгоритм цепей Маркова для префиксов из 2 слов
BEGIN { MAXGEN = 10000; NONWORD = "\пм; w1 = w2 = NONWORD }
{ for (i = 1; i <= NF; i++) { # читать все слова
Statetab[w1,w2,++nsuffix[w1,w2]] = $i
w1 = w2
w2 = $i
}
}
END {
Statetab[w1,w2,++nsuffix[w1,w2]] = NONWORD
# добавить метку конца ввода
w1 = w2 = NONWORD
for (1=0; i < MAXGEN; i++) { # генерируем
r = int(rand()*nsuffix[w1,w2]) + 1 # nsuffix >= 1
p = statetab[w1,w2, r]
if (p == NONWORD)
exit
print p
wl = w2 # идти дальше по цепочке
w2 =.p
}
}
Awk — язык, функционирующий по принципу "образец-действие": входной поток читается по строке за раз, каждая строка сравнивается с образцом, для каждого совпадения выполняется соответствующее действие. Существуют два специальных образца — BEGIN и END, которые вызываются, соответственно, перед первой строкой ввода и после последней.
Действие — это блок выражений, заключенный в фигурные скобки. В Awk-версии в блокеBEGIN инициализируется префикс и пара других переменных.
Следующий блок не имеет образца, поэтому он по умолчанию вызывается для каждой новой строки ввода. Awk автоматически разделяет каждую вводимую строку на поля (ограниченные пробелами слова), имеющие имена от $1 до $NF; переменная NF — это количество полей. Выражение
statetab[w1,w2,++nsuffix[w1,w2]] = $i
создает отображение префиксов в суффиксы. Массив nsuffix считает суффиксы, а элемент
nsuffix[w1, w2] считает количество суффиксов, ассоциированных с префиксом. Сами суффиксы хранятся в элементах массива statetab[w1, w2,1], statetab[w1, w2, 2] и т. д.
Блок END вызывается на выполнение после того, как весь ввод был считан. К этому моменту для каждого префикса существует элемент nsuffix, содержащий количество суффиксов, и, соответственно, существует именно столько элементов statetab, содержащих сами суффиксы.
Версия Perl выглядит похожим образом, но в ней для хранения суффиксов используется безымянный массив, а не третий индекс; для обновления же префикса используется множественное присваивание. В Perl для обозначения типов переменных применяются специальные символы: $ обозначает скаляр, @ — индексированный массив, квадратные скобки [ ] используются для индексации массивов, а фигурные скобки { } — для индексации хэшей.
# markov.pl: алгоритм цепей Маркова для префиксов из 2 слов
$MAXGEN = 10000;
$NONWORD = “\n”;
$w1 = $w2 = $NONWORD; # начальное состояние
while (<>) { # читать каждую строку ввода
forech (split) {
push(@{$statetab{$w1}{$w2}}, $_);
($w1, $w2) = ($w2, $_); # множественное присваивание
}
}
push(@{$statetab{$w1}{$w2}}, $NONWORD);
# добавить метку конца ввода
$w1 = $w2 = $NONWORD;
for ($i =0; $i < $MAXGEN; $i++) {
$suf = $statetab{$w1}{$w2}; # ссылка на массив
$r = int(rand @$suf); # @$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 так, чтобы они могли обрабатывать префиксы произвольной длины. Попробуйте определить, как это скажется на быстродействии программ.
