
- •4. Текстовые файлы и циклическое выполнение
- •4.1 Текстовые файлы
- •4.1.1. Операции над файлами.
- •4.1.2. Синтаксис и семантика файлов.
- •4.1.3. Копирование файлов
- •4.1.4. Разделение файлов.
- •4.2. Сортировка и использованием циклического выполнения.
- •4.2.1. Программа SelectSort
- •4.2.2. Структурное тестирование
- •4.2.3. Анализ SelectSort
- •4.3. Маркеры текстовых файлов
- •4.3.1. Маркер конца строки
- •4.3.2. Маркер конца файла
- •4.3.3. Копирование строк
- •Dp4.4 { Копируем f2 в f1 } и dp4.3 { Копируем f1 в output }
- •4.4. Заключение
4.2.2. Структурное тестирование
Тестирование программ чтобы иметь уверенность в том, что проектирование и разработка развиваются так, как запланировано, является разумным подходом. Не всегда понятно, какие части проекта следует собирать в первую очередь, какую отладочную информацию распечатывать, самое сложное - какие тестовые данные выбрать для отладки, чтобы иметь возможность контролировать появление ошибок и т.д. Нет гарантии, что тестирование найдет все возможные трудности программы.
Каждый, кто занимался практическим программированием согласится с утверждением Edsger Dijkstra что тестирование позволяет обнаружить присутствие ошибок, а не их отсутствие.
Тем не менее, сильной стороной тестирования является его более или менее механистичная природа, поэтому важно иметь систематический подход к выбору данных для тестирования.
При структурном тестировании данные выбираются исходя из структуры программы. Простейший вариант – таким образом подобрать тестовые данные, чтобы каждое выражение выполнилось хотя бы однажды. Единственный аргумент в пользу такого подхода следующий: если при тестировании выражение не было выполнено ни разу, значит, там могут скрываться ошибки, которые могут проявиться позднее. Обратное не верно, поскольку даже если каждое выражение было выполнено, все равно в коде могут присутствовать ошибки, которые не были выявлены тестами.
При всех его недостатках, структурное тестирование предлагает некий систематический подход к подготовке тестовых данных. Если у программиста есть идеи, что в структуре приложения или какие части кода могут быть источником ошибок и требуют углубленного тестирования, тесты, специально подготовленные для таких случаев, могут быть очень полезны. Когда идеи отсутствуют, структурное тестирование позволяет частично решить проблему.
Для примера рассмотрим тест для DP 3A с входными данными XBZA. Очевидно, что все выражения будут выполнены, единственные части которые могут быть пропущены – операторы внутри выражений WHILE, для которых условие на входе равно FALSE.
Тестовый запуск программы 3B со входными данными CEDAR# проанализировать несколько более сложно. Для всех выражений WHILE выполняются части DO, но не понятно выполняются ли части THEN и ELSE внутри DP 3.2.1.1 В любом случае символ записан в F2, но это не дает нам информации, каким путем шло выполнение внутри оператора IF.
Часть THEN выполняется когда меняется текущий минимум, это происходит при первом проходе для CEDAR#, когда С заменяется на A при четвертой итерации для 3.2.1.1. С другой стороны, часть ELSE выполняется когда текущий минимум не меняется и это случается на второй итерации, когда C меньше чем E. Таким образом, CEDAR является адекватным структурным тестом. Анализ также показывает, что XHB# не может быть адекватным структурным тестом, поскольку часть ELSE для 3.2.1.1 не может быть достигнута.
4.2.3. Анализ SelectSort
В противоположность семейству IFSort и MinSort, размеры которых существенно растут с ростом размеров сортируемых строк, SelectSort может сортировать строки любого размера. Будучи по размеру не более, чем IFSort4 или MinSort6, SelectSort может сортировать строки в сотни и тысячи символов. Однако, время выполнения для SelectSort будет зависеть от длины входной строки. Количество шагов в данном случае может быть точно определено. Первое, сосредоточимся на выражениях WHILE, которые появляются в DP 3.1, 3.2, 3.2.1, 3.2.2. Исключая другие детали программы, выражения WHILE в SelectSort могут быть пронумерованы.
Номер WHILE |
Структура и решаемые задачи |
1
2
3
4
|
BEGIN … WHILE {Копировать INPUT в F1} … WHILE {Продолжать, пока F1 не пустой} BEGIN … WHILE {Копировать все кроме мин. из F1 в F2} … WHILE {Копировать F2 в F1} … END … END |
Предположим, что имеется N входных символов. Выражение WHILE номер 1 требует N итераций. Второе выражение WHILE также потребует N итераций, потому что F1 укорачивается на 1 символ при каждой итерации. Количество итерации для третьего и четвертого выражений WHILE зависит от номера итерации для выражения WHILE номер 2.
На первой итерации WHILE номер 2, WHILE номер 3 требует N итераций, а WHILE номер 4 – N-1 итераций. На второй итерации для WHILE номер 2, WHILE номер 3 требует N-1 итераций, а WHILE номер 4 – N-2 итераций и т.д. Таким образом, количество итераций будет следующим:
Выражение WHILE |
Количество итераций |
1 2 3 4 |
N N N + N-1+ … + 1 N-1 + N-2 + … + 1 + 0 |
Далее, посчитаем выражения READ и WRITE для каждого блока WHILE
Выражение WHILE |
Операций READ/WRITE |
1 2 3 4 |
2 1 2 2 |
Следовательно, количество выражений READ/WRITE выполняемых SelectSort, будет следующим:
Nrw = 2N + N + 2(N + (N-1) + … + 1) + 2((N-1) + (N-2) + … + 0)
= 5N + 4((N-1) + …+ 1)
= 5N + 4N(N-1)/2
= 5N + 2N2 – 2N
= 2N2+3N
Количество выражений READ/WRITE для входных последовательностей различной длины
N |
Nrw |
2N2 |
5 10 20 100 1000 |
65 230 860 20 300 2 003 000 |
50 200 800 20 000 2 000 000 |
Очевидно, что количество итераций Nrw в основном определяется составляющей 2N2. Мы можем сказать, что количество операций ввода-вывода для данной программы порядка 2N2. Т.е. 1000-строка будет сортироваться примерно в 100 раз дольше, чем 100-строка, и т.д. Время работы программы может быть довольно большим для еще более длинных строк.
Реверсирование строк с помощью выбора.
Программа SelectSort требует небольшой переделки чтобы стать программой для обращения строк. Единственная модификация, которая требуется – заменит DP 3.2.1 (которая выбирает минимальный из 1) на раздел, который может быть обозначен следующим комментарием:
{Выбрать последний символ в F1,
копировать остаток в F2}
Также в данном случае имеет смысл переименовать переменную Min в Last, чтобы название переменной соответствовало ее использованию в программе.
Повышение быстродействия SelectSort и SelectReverce
Скорость SelectSort и SelectReverce может быть увеличена примерно вдвое, если использовать F1 и F2 симметрично. Выбирать символ из F1 и копировать остаток в F2, затем выбирать символ из F2 и копировать остаток в F1 и т.д. Для того, чтобы определить, из какого файла читать и в какой копировать потребуется переключатель even/odd (четное/нечетное).
На самом деле, существует еще один способ ускорения SelectSort. Мы можем использовать внутренние переменные для того, чтобы хранить не один текущий минимум, а несколько на один проход. Например, с переменными Min1, Min2, Min3, Min4, Min5 пять минимальных хначений может быть найдено и выведено за один проход. Таким образом мы можем ускорить SelectSort примерно в 5 раз.
Используя оба приведенных приема, например с 10 переменными для хранения локальных минимумов и симметричного использования файлов, мы можем увеличить скорость работы программы в 20 раз, т.е. сократить время работы программы, например с 3 часов до 10 минут.