Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Михалкович С.С. Основы программирования

.pdf
Скачиваний:
82
Добавлен:
19.05.2015
Размер:
556.43 Кб
Скачать

read(f,x);

x:=x*x;

write(f1,x); end; Close(f1); Close(f);

end;

Пример 3. На диске хранится файл 'group.dat', содержащий записи о студентах. Каждая запись состоит из следующих полей: имя студента (типа string[30]), курс и группа. Требуется найти запись о студенте Иванове и перевести его в 10 группу.

Данный пример иллюстрирует использование типизированных файлов для программирования простейших баз данных.

Решение.

const name = 'group.dat';

type

Student = record name: string[30];

course,group: integer; end;

var

f: file of Student; s: Student;

begin

Assign(f,name);

Reset(f); ind:=-1;

for i:=0 to FileSize(f)-1 do // поиск begin

read(f,s);

if s.name = 'Иванов' then begin

s.group:=10;

Seek(f,i);

write(f,s);

break; end;

end; Close(f);

end.

13

Пример 4. Составить процедуру, сортирующую методом «пузырька» элементы файла целых чисел по возрастанию.

Решение.

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

type RealFile=file of real;

procedure SortFile(var f: RealFile); var i,j: integer;

x,y: real; begin

for i:=FileSize(f)-1 downto 1 do for j:=0 to i-1 do

begin

Seek(f,j);

read(f,x,y); if x>y then begin

Seek(f,j);

write(f,y,x); end;

end; end;

Следует обратить внимание на уже отмеченный факт, что файловую переменную можно передавать в подпрограмму только по ссылке.

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

2.6 Текстовые файлы

Напомним, что текстовые файлы имеют тип text и состоят из строк разной длины, разделяемых маркером конца строки EOLN. Символ с кодом 26 воспринимается как признак конца текстового файла. Будем называть его маркером конца текстового файла и обозначать EOF. При обращении к файлу из программы считается, что в конце файла обязательно находится символ EOF, даже если он отсутствует в физическом файле. Например, файл, состоящий из четырех строк

14

var i: integer; begin

end.

следующим образом воспринимается Паскаль-программой:

var i: integer;<EOLN>begin<EOLN><EOLN>end.<EOF>

Подпрограммы работы с текстовыми файлами имеют ряд особенностей. Процедура Reset(f) открывает текстовый файл f только на чтение, а процедура Rewrite(f) – только на запись.

Процедура Write в качестве параметров может содержать любые выражения целого, вещественного, символьного, строкового или логического типа; переменные тех же типов, кроме логического, могут присутствовать в операторе ввода Read. Вывод осуществляется в текстовом виде, при этом можно использовать форматы вывода вида :w (w – целое число, задающее ширину поля вывода) и для вывода вещественных значений – формат :w:d (d – целое число, задающее количество цифр после десятичной точки; если d=0, то десятичная точка не выводится).

При вводе чисел данные должны быть отделены друг от друга пробелами, символами табуляции или символами перехода на новую строку. Если при вводе числа файловый указатель находится на таком символе-разделителе, он пропускает все символы-разделители до первого значащего символа (символа с кодом больше 32), после чего пытается прочитать число. При неудачной попытке и включенном контроле за ошибками ввода-вывода {$I+} в Delphi либо генерируется исключение (если подключен модуль SysUtils), либо происходит ошибка времени выполнения (если модуль SysUtils не подключен).

В отличие от двоичных файлов, считывание за пределами текстового файла не приводит к ошибке (в частности, функция IOResult возвращает 0), файловый указатель при этом не перемещается. При чтении за концом файла в символьную переменную записывается символ конца файла (#26), в числовую – нулевое значение, в строковую – пустая строка.

Как и при вводе с клавиатуры и выводе на экран, для текстовых файлов можно использовать процедуры Writeln и Readln. Процедура Writeln после вывода вставляет в текстовый файл маркер конца строки. В частности, Writeln(f) просто осуществляет переход на новую строку в файле f. Процедура Readln после ввода пропускает все символы до конца строки включительно (вместе с символом EOLN). В частности Readln(f), просто переставляет файловый указатель в начало новой строки.

Далее перечислим стандартные подпрограммы, предназначенные для работы только с текстовыми файлами.

15

Append(f) – процедура, открывающая текстовый файл в режиме добавления. Файловый указатель при этом устанавливается на маркер конца файла. Если файл не существует, то происходит ошибка времени выполнения.

Eoln(f) – функция, возвращающая True, если файловый указатель стоит на маркере конца строки.

SeekEof(f) – функция, пропускающая все пробелы, символы табуляции и символы перехода на новую строку, после чего возвращающая то же, что и

Eof(f).

SeekEoln(f) – функция, пропускающая все пробелы и символы табуляции, после чего возвращающая то же, что и Eoln(f).

Две последние функции обычно используются для ввода чисел.

Для текстовых файлов подпрограммы Truncate, Seek, FileSize, FilePos не применяются: их использование приведет к ошибке компиляции.

Рассмотрим несколько примеров, иллюстрирующих основные действия с текстовыми файлами.

Пример 1. Дан текстовый файл a.txt, содержащий целые числа, разделенные любым количеством пробелов или символов перехода на новую строчку. Найти их количество и сумму.

Приведем пример подобного файла:

5 3 <EOLN> 4<EOLN>

77 8 <EOF>

Решение 1. Поэлементная обработка.

Вместо Eof будем использовать функцию SeekEof, которая пропускает пробельные символы перед тем как проанализировать состояние конца файла. Это важно при считывании последнего числа: после считывания числа 8 файловый указатель будет находиться на пробеле после него; вызов SeekEof продвинет указатель на конец файла и вернет True. Для обработки возможных ошибок ввода окаймим также тело цикла блоком try с пустой секцией except: в случае ошибки в файле нечисловые символы будут пропущены. Далее приводится полный текст первого варианта решения.

uses SysUtils; var f: text;

s,c,x: integer; begin

Assign(f,'a.txt');

Reset(f);

s:=0;

c:=0;

while not SeekEof(f) do

16

try read(f,x); s:=s+x; c:=c+1;

except end;

Close(f); writeln(c,' ',s);

end.

Отметим, что если использовать функцию Eof вместо SeekEof, то при наличии пробелов в конце файла после считывания последнего числа Eof(f) вернет False и на следующей итерации цикла вызов read(f,x) запишет в переменную x число 0. В результате количество целых чисел c будет на 1 больше правильного.

Решение 2. Посимвольная обработка.

Вторая стратегия решения заключается в посимвольном считывании и накапливании в некоторой строковой переменной s текущей лексемы. Лексемой здесь мы будем называть любую последовательность символов, не являющихся разделителями. Если считанный символ является разделителем и лексема в строке s не пуста, то она считается сформированной и обрабатывается (преобразуется в число), после чего строка s обнуляется. Если же считанный символ не является разделителем, то он просто добавляется к строке s. Преобразование лексемы в число можно осуществить процедурой Val, что позволяет обойти использование механизма исключений. Далее приводится полный текст второго варианта решения.

var f: text; ch: char; i: integer;

begin

Assign(f,'a.txt');

Reset(f);

sum:=0;

s:=''; repeat

read(f,ch);

if not (ch in [#0..#32]) then s:=s+ch

else if s<>'' then begin

Val(s,i,errcode); if errcode=0 then

sum:=sum+i;

17

s:=''; end

until Eof(f); Close(f);

end.

Заметим, что в данной задаче условие Eof(f) эквивалентно условию ch=#26. Заметим также, что разбиение на лексемы в данном варианте решения можно использовать в ряде других задач (связанных, например, с разбиением текста на слова).

Пример 2. Преобразовать строки текстового файла, воспользовавшись для преобразования каждой строки пользовательской функцией Convert.

Решение. В отличие от предыдущего примера, где мы пользовались поэлементным или посимвольным считыванием, будем считывать строку целиком, после чего ее обрабатывать. Такое решение естественно назвать построчной обработкой. Результат преобразования будем записывать во вспомогательный файл (имя ему дадим произвольно). В конце программы решение содержится во вспомогательном файле, поэтому удалим основной файл и дадим вспомогательному файлу имя основного:

var f,f1: text; // f1-вспомогательный файл s: string;

begin

Assign(f,'a.txt');

Reset(f); Assign(f1,'$tmp.dat'); Rewrite(f1);

while not Eof(f) do begin

readln(f,s);

s:=Convert(s);

writeln(f1,s) end

Close(f);

Close(f1);

Erase(f);

Rename(f1,'a.txt'); end.

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

18

второго, будут возвращать пустую строку, а файловый указатель продвигаться не будет. Кроме того, после прерывания зациклившейся программы на диске останется большой временный файл $tmp.dat.

В заключение заметим, что данный каркас решения можно использовать и для родственных задач. Например, если требуется вычислить количество и сумму чисел в файле (как в примере 1), то в данном решении достаточно заменить функцию Convert процедурой подсчета количества и суммы чисел в строке и изъять все строки, связанные со вспомогательным файлом.

2.7 Алгоритмы внешней сортировки

Алгоритмы внешней сортировки – это алгоритмы, позволяющие сортировать данные во внешней памяти. Они ориентированы на последовательный перебор элементов и поэтому ранее широко применялись именно для сортировки файлов. Однако эти алгоритмы могут быть использованы для любых последовательно организованных данных: например, для массивов и списков. Кроме того, данные алгоритмы по скорости работы сравниваются с алгоритмом быстрой сортировки и потому являются одними из лучших. Реализация этих алгоритмов, однако, является достаточно громоздкой, поэтому в настоящем пункте приведем лишь сам алгоритм.

Далее будем предполагать, что используемые файлы имеют только последовательный доступ. Сами файлы будем называть также лентами (по аналогии с лентой магнитофонной кассеты, доступ к которой может быть только последовательным).

Трехленточная двухфазовая сортировка слиянием

Пусть исходные данные хранятся в файле C, и количество данных есть степень двойки. Приводимый ниже алгоритм использует на каждом шаге две фазы (разделение и слияние) и три ленты (файла). Отсюда его название – трехленточ-

ная двухфазовая сортировка слиянием.

Разобьем данные на серии. Серией будем называть группу подряд идущих отсортированных элементов.

I шаг. На первом шаге в каждой серии будет содержаться один элемент.

C: 23 │ 12 │ 49 │ 96 │ 14 │ 69 │ 73 │ 28

I фаза. Разделение.

Файлы A и B очищаются. Серии в файле C с нечетными номерами записываются в файл A, а с четными – в файл B.

A:23 │ 49 │ 14 │ 73

B:12 │ 96 │ 69 │ 28

19

II фаза. Слияние.

Файл C очищается. Серии элементов с одинаковыми номерами в файлах A и B сливаются в одну серию с помощью алгоритма слияния двух упорядоченных последовательностей в одну и дописываются в конец файла C.

C:12 23 │ 49 96 │ 14 69 │ 28 73

II шаг. В каждой серии – два элемента.

Iфаза. Разделение.

A:12 23 │ 14 69

B:49 96 │ 28 73

IIфаза. Слияние.

C:12 23 49 96 │ 14 28 69 73

III шаг. В каждой серии – четыре элемента.

Iфаза. Разделение.

A:12 23 49 96

B:14 28 69 73

IIфаза. Слияние.

C:12 14 23 28 49 69 73 96

Заметим, что в каждой фазе совершается один последовательный проход по каждому файлу. Отметим также, что если количество элементов в файле C равно n, то количество шагов алгоритма равно log2 n . Поскольку на каждом шаге со-

вершается два прохода по файлу C с n элементами, то количество операций при сортировке слиянием имеет временную сложность n log2 n (как и для быстрой сортировки).

Четырехленточная однофазовая сортировка слиянием

В предыдущем алгоритме вторая фаза неэффективна – данные просто переписываются из одного файла в другой. От этой фазы можно избавиться, введя дополнительную ленту (файл). Для этого на фазе слияния следует записывать данные не в один файл, а попеременно в два, а затем использовать эти файлы в качестве исходных на следующем шаге.

Пусть первоначально файл C содержит те же данные, что и в предыдущем пункте.

C: 23 12 49 96 14 69 73 28

На первом шаге данные из файла C следует разбить на два файла, попеременно записывая их в файл A и файл B:

A:23 │ 49 │ 14 │ 73

B:12 │ 96 │ 69 │ 28

Произведем слияние серий. Серии с нечетными номерами будем сливать в первый файл, с четными – во второй.

20

C:12 23 │ 14 69

D:49 96 │ 28 73

Повторим слияние серий, используя полученные файлы в качестве исходных.

A:12 23 49 96

B:14 28 69 73

На последнем шаге полученные два файла следует слить в один, записав элементы второго файла в конец первого.

C: 12 14 23 28 49 69 73 96

Отметим, что на каждом шаге по каждому файлу мы совершаем один последовательный проход.

Естественное слияние

В двух предыдущих алгоритмах не учитывается упорядоченность элементов. Эти алгоритмы работают одинаковое время для любых последовательностей данных, в частности, если данные уже отсортированы. Предложим более совершенный алгоритм, в котором учитывается уже имеющаяся упорядоченность элементов. Такой алгоритм называется естественным слиянием.

Пусть данные находятся в файле C. Разобьем их на серии упорядоченных элементов.

C: 17 19 21 │ 13 57 67 │ 23 29 │ 11 59 61 │ 7 31 37 │ 2 43 67 │ 5

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

I шаг.

Iфаза. Разделение.

A:17 19 21 │ 23 29 │ 7 31 37 │ 5

B:13 57 67 │ 11 59 61 │ 2 43 67

Обратим внимание, что две первых серии в массиве A могут быть объединены в одну:

A:17 19 21 23 29 │ 7 31 37 │ 5

IIфаза. Слияние.

C:13 17 19 21 23 29 57 67 │ 7 11 31 37 59 61 │ 2 5 43 67

II шаг.

I фаза.

A:13 17 19 21 23 29 57 67 │ 2 5 43 67

B:7 11 31 37 59 61

IIфаза.

C:7 11 13 17 19 21 23 29 31 37 57 59 61 67 │ 2 5 43

67

21

III шаг.

Iфаза.

A:7 11 13 17 19 21 23 29 31 37 57 59 61 67

B:2 5 43 67

IIфаза.

C:2 5 7 11 13 17 19 21 23 29 31 37 43 57 59 61 67 67

Разбиение на серии не обязательно проводить специально – это можно делать в процессе слияния. Очевидно, серия заканчивается, если следующий элемент либо отсутствует, либо меньше предыдущего. Заметим также, что для сортировки нам потребовалось всего 3 шага, несмотря на то, что исходная последовательность состоит из 18 элементов, а не из 8, как в предыдущем пункте.

2 Рекурсия

2.1 Основные определения

Рекурсия – это способ определения объекта, при котором он частично определяется через такой же объект. Определение, содержащее рекурсию, называется

рекурсивным определением. Например, натуральное число – это либо 1, либо целое число, следующее за натуральным.

Рекурсивные определения удобно записывать с помощью форм БэкусаНаура:

Массив ::= <пусто> | элемент Массив

СписокПараметров ::= параметр | СписокПараметров ’,’ параметр

Обратим внимание, что во всех предыдущих примерах объект при некотором условии определяется нерекурсивно. Это условие является признаком окончания рекурсии. Обратим также внимание, что рекурсивный объект может стоять как в начале рекурсивного определения (леворекурсивное определение), так и в конце

(праворекурсивное определение).

Рекурсивно можно определять не только объекты, но и математические функции. Функция называется рекурсивной, если ее значение при некоторых значениях аргументов определяется через значение этой функции при других значениях аргументов. Например:

n (n 1)!, åñëè n > 0, n!= 1, åñëè n = 0.

a n = a a n1 , åñëè n > 0,1, åñëè n = 0.

22