PascalABC.NET

Задание на обработку файлов


Создание программы-заготовки и знакомство с заданием

В качестве примера задания на обработку файлов рассмотрим задание File48.

Напомним, что программу-заготовку для решения этого задания можно создать с помощью команды меню «Модули | Создать шаблон программы», кнопки или клавиатурной комбинации Shift+Ctrl+L. Эта заготовка будет иметь следующий вид:

uses PT4;

begin
  Task('File48');

end.

После запуска данной программы на экране появится окно задачника:

В первой строке раздела исходных данных указаны имена трех исходных файлов (SA, SB и SC) и одного результирующего (SD). В последующих строках раздела исходных данных показано содержимое исходных файлов. Элементы файлов отображаются бирюзовым цветом, чтобы подчеркнуть их отличие от обычных исходных данных (желтого цвета) и комментариев (светло-серого цвета).

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

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


Ввод части исходных данных

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

uses PT4;
var
  i: integer;
  s: string;
  f: array [1..4] of file of integer;
begin
  Task('File48');
  for i := 1 to 3 do
  begin
    read(s);
    Assign(f[i], s);
  end;
end. 

Мы намеренно ограничились тремя итерациями цикла, оставив непрочитанным имя результирующего файла. Считывание имен файлов производится в одну и ту же переменную s, поскольку после связывания файла, имеющего имя s, с соответствующей файловой переменной (процедурой Assign) все остальные действия с данным файлом в нашей программе будут осуществляться с использованием файловой переменной, без обращения к имени файла.

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

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


Ввод всех исходных данных без создания требуемого файла

Изменим программу, заменив в заголовке цикла число 3 на 4, и вновь запустим программу. Теперь все данные, необходимые для выполнения задания, введены в программу (это видно по индикатору ввода). Однако задание не выполнено, поскольку результирующий файл не создан. Поэтому решение опять признано ошибочным с диагностикой «Результирующий файл не найден»:


Пример программы, приводящей к ошибке времени выполнения

Добавим в тело цикла после процедуры Assign вызов процедуры Reset, обеспечивающий открытие существующего файла:

uses PT4;
var
  i: integer;
  s: string;
  f: array [1..4] of file of integer;
begin
  Task('File48');
  for i := 1 to 4 do
  begin
    read(s);
    Assign(f[i], s);
    Reset(f[i]);
  end;
end.  

Теперь запуск программы приведет к сообщению об ошибке «Error System.IO.FileNotFoundException»:

Сообщение, начинающееся со слова Error, означает, что при работе программы произошла ошибка времени выполнения (Runtime Error). После слова Error указывается имя этой ошибки (в данном случае System.IO.FileNotFoundException, то есть ошибка ввода-вывода, связанная с тем, что файл не найден) и краткое ее описание на английском языке.

Сообщение об ошибке времени выполнения появится и в разделе «Список ошибок» окна PascalABC.NET:


Создание пустого результирующего файла

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

uses PT4;
var
  i: integer;
  s: string;
  f: array [1..4] of file of integer;
begin
  Task('File48');
  for i := 1 to 4 do
  begin
    read(s);
    Assign(f[i], s);
    if i < 4 then Reset(f[i])
    else Rewrite(f[i]);
  end;
  { * }
  for i := 1 to 4 do
    Close(f[i]);
end.  

Комментарий { * } расположен в том месте программы, в котором можно выполнять операции ввода-вывода для всех четырех файлов: они уже открыты процедурами Reset или Rewrite и еще не закрыты процедурой Close.

Запуск этого варианта программы не приведет к ошибке времени выполнения; более того, результирующий файл будет создан. Однако созданный файл останется пустым, то есть не содержащим ни одного элемента. Поэтому при запуске программы на информационной панели появится сообщение «Ошибочное решение», а в строке, которая должна содержать элементы результирующего файла, появится текст EOF: (особое значение EOF для указателя текущей файловой позиции означает, что данный файл существует, но не содержит ни одного элемента):


Пример программы, использующей неправильные типы для файловых данных

Во всех ранее рассмотренных вариантах программы мы не использовали операции ввода-вывода для файлов. Поэтому тип файлов не играл никакой роли: вместо типа file of integer мы могли использовать любой другой файловый тип, например, file of real, и результат выполнения программы был бы тем же самым.

Тип файловых элементов становится принципиально важным, если в программе используются операции ввода-вывода для данного файла. Чтобы продемонстрировать это на примере нашей программы, внесем в нее следующие изменения: в описании массива f файловых переменных тип integer заменим на real, в раздел описаний добавим описание переменной a типа real, в раздел операторов (в позицию, помеченную комментарием { * }) добавим следующий фрагмент:

for i := 1 to 3 do
begin
  read(f[i], a);
  write(f[4], a);
end;

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

Результат работы программы будет неожиданным:

Судя по экранной строке с содержимым результирующего файла, в него будут записаны не три, а шесть элементов, по два начальных элемента из каждого исходного файла. Объясняется это тем, что после связывания файлов с файловыми переменными типа file of real элементами файлов стали считаться вещественные числа (занимающие в памяти по 8 байтов), тогда как «на самом деле», то есть по условию задания, элементами файлов являются целые числа (занимающие в памяти по 4 байта). Поэтому считывание из файла и последующая запись в файл одного «вещественного элемента» фактически приводит к считыванию и записи блока данных размером 8 байтов, содержащего два последовательных целочисленных элемента исходного файла.

Итак, мы выяснили, что ошибки, связанные с несоответствием типов файлов, не выявляются при компиляции и не всегда приводят к ошибкам времени выполнения. Это следует иметь в виду, и при появлении «странных» результирующих данных начинать поиск ошибки с проверки типов файловых переменных.


Исправление ошибки, связанной с неверными типами файловых данных

Заменим в нашей программе все описания real на integer:

uses PT4;
var
  i: integer;
  s: string;
  f: array [1..4] of file of integer;
  a: integer;
begin
  Task('File48');
  for i := 1 to 4 do
  begin
    read(s);
    Assign(f[i], s);
    if i < 4 then Reset(f[i])
    else Rewrite(f[i]);
  end;
  for i := 1 to 3 do
  begin
    read(f[i], a);
    write(f[4], a);
  end;
  for i := 1 to 4 do
    Close(f[i]);
end.  

Мы получим все еще неверное, но вполне «понятное» решение: первые три элемента результирующего файла совпадают с контрольными (то есть «правильными»), а прочие элементы отсутствуют:


Верное решение

Приведем, наконец, верное решение задания File48:

uses PT4;
var
  i, a: integer;
  s: string;
  f: array [1..4] of file of integer;
begin
  Task('File48');
  for i := 1 to 4 do
  begin
    read(s);
    Assign(f[i], s);
    if i < 4 then Reset(f[i])
    else Rewrite(f[i]);
  end;
  while not Eof(f[1]) do
    for i := 1 to 3 do
    begin
      read(f[i], a);
      write(f[4], a);
    end;
  for i := 1 to 4 do
    Close(f[i]);
end.  

От предыдущего варианта данное решение отличается добавлением заголовка цикла while not Eof(f[1]) do, который обеспечивает считывание всех элементов из исходных файлов (напомним, что по условию задания все исходные файлы имеют одинаковый размер) и запись их в результирующий файл в нужном порядке. После запуска этого варианта мы получим сообщение «Верное решение. Тест номер 1 (из 5)», а после пяти подобных запусков — сообщение «Задание выполнено!»:


Просмотр результатов выполнения задания

Щелкнув мышью на метке «Результаты (F2)», расположенной в правом верхнем углу окна задачника, или нажав клавишу F2, мы можем вывести на экран окно результатов, в котором будет перечислены все наши попытки решения задачи:

File48    a08/09 12:43 Ознакомительный запуск.
File48    a08/09 12:50 Введены не все требуемые исходные данные.
File48    a08/09 12:52 Результирующий файл не найден.
File48    a08/09 12:53 Error System.IO.FileNotFoundException.
File48    a08/09 12:57 Ошибочное решение.--3
File48    a08/09 13:06 Задание выполнено!

Для закрытия окна результатов достаточно нажать клавишу Esc. Окно результатов можно отобразить на экране и после закрытия окна задачника и возврата в среду PascalABC.NET. Для этого надо использовать команду меню «Модули | Просмотреть результаты», кнопку или клавиатурную комбинацию Shift+Ctrl+R.