W Cat - Описание языка PascalABC.NET
var
f1,f2: text;
f1name, f2name: string;
procedure StartExam;
var
s: string;
begin
Str(RandomN(10000, 99999), s);
f1name := 'pt1' + s + '.tst';
f2name := 'pt2' + s + '.tst';
Assign(f1, f1name);
Rewrite(f1);
Assign(f2, f2name);
Rewrite(f2);
end;
procedure EndExam;
begin
Close(f1);
Close(f2);
DataS(f1name, 3, 1);
DataS(f2name, 45, 1);
DataFileT(f1name, 1, 5);
ResultFileT(f2name, 1, 5);
end;
Обсудим особенности этих процедур. Имена файлов, создаваемых в процедуре StartExam, имеют вид pt1#####.tst (для файла с исходными данными) и pt2#####.tst (для файла с контрольными данными), причем в позициях, помеченных символом #", располагаются цифры, выбираемые случайным образом. Тем самым обеспечиваются все требования к именам файлов: они генерируются случайным образом, имеют расширение .tst, и имя файла с исходными данными всегда отличается от имени контрольного файла. Напомним, что все файлы с расширением .tst автоматически удаляются из рабочего каталога после проверки учебного задания.
При анализе процедуры EndExam следует обратить внимание на то, что информация о содержимом исходного файла занимает всю область исходных данных (строки с первой по пятую -- см. вызов процедуры DataFileT) и, таким образом, она скрывает информацию об именах файлов, ранее выведенную в первой строке области исходных данных (см. вызовы процедур DataS). В обычном задании такая реализация была бы ошибочной, поскольку учащийся не увидел бы на экране имена файлов и не понял бы, что эти имена необходимо ввести и обработать в его программе. Однако в задании групп Exam именно такая реализация является правильной, поскольку ввод имен файлов и связывание этих файлов со стандартными потоками ввода-вывода выполняется автоматически (незаметно" для программы учащегося), и поэтому информацию об именах файлов на экране отображать не следует.
Итак, наличие процедур StartExam и EndExam позволяет нам упростить реализацию заданий: после определения формулировки любого задания нам достаточно вызвать процедуру StartExam, заполнить файлы f1 и f2 исходными и, соответственно, контрольными данными и вызвать процедуру EndExam.
Приступим к непосредственной реализации заданий. Поскольку эти задания являются однотипными, реализуем их в одной процедуре Exam1, снабдив ее параметром m: при m = 1 будет инициализироваться первое задание, а при m = 2 -- второе:
procedure Exam1(m: integer);
var
n, i: integer;
a: array[1..10] of real;
begin
CreateTask('Преобразование массивов');
case m of
1:
begin
TaskText('На вход в первой строке подается целое положительное четное число {N},', 0, 1);
TaskText('а во второй строке = массив из {N} вещественных чисел. Поменять местами', 0, 2);
TaskText('его первый элемент со вторым, третий с четвертым, и т.,д. Вывести', 0, 3);
TaskText('преобразованный массив в одной строке, для каждого элемента', 0, 4);
TaskText('отводить 7 экранных позиций.', 0, 5);
end;
2:
begin
TaskText('На вход в первой строке подается целое положительное четное число {N},', 0, 2);
TaskText('а во второй строке = массив из {N} вещественных чисел. Поменять местами', 0, 3);
TaskText('первую и вторую половину элементов массива. Вывести преобразованный массив', 0, 4);
TaskText('в одной строке, для каждого элемента отводить 7 экранных позиций.', 0, 5);
end;
end;
StartExam;
n := 2 * RandomN(1, 5);
for i := 1 to n do
a[i] := RandR(-99, 99);
writeln(f1,n);
for i := 1 to n - 1 do
write(f1, a[i]:0:2, ' ');
writeln(f1, a[n]:0:2);
for i := 1 to n div 2 do
case m of
1: SwapR(a[2*i - 1], a[2*i]);
2: SwapR(a[i], a[i + n div 2]);
end;
for i := 1 to n do
write(f2, a[i]:7:2);
writeln(f2);
EndExam;
SetTestCount(3);
end;
Обратите внимание на то, что при вызове процедуры CreateTask ей передается строковый параметр, содержащий имя подгруппы "Преобразование массивов". Это обеспечивает включение новых заданий в подгруппу, с которой связаны ранее импортированные в нашу группу задания ExamBegin71 и ExamBegin72.
Размер исходного массива всегда будет четным и не превосходящим 10; последнее условие необходимо для того, чтобы все исходные данные можно было разместить на одной экранной строке.
В обоих заданиях значения элементов исходного массива можно выбирать произвольным образом из некоторого диапазона. Мы выбрали диапазон от -99 до 99, поскольку в этом случае при отображении чисел с двумя дробными знаками они будут занимать не более 6 экранных позиций.
При записи в файл элементов исходного массива между ними всегда располагается по одному пробелу, поскольку такой порядок организации исходных данных принят во всех заданиях групп ExamBegin и ExamTaskC. Чтобы обеспечить при этом отображение вещественных чисел с двумя дробными знаками, используется специальный набор форматирующих атрибутов: ":0:2". При выводе результатов, согласно формулировке задания, необходимо отводить для каждого элемента массива по 7 экранных позиций и выводить его с двумя дробными знаками (последнее условие принято по умолчанию во всех заданиях групп ExamBegin и ExamTaskC, использующих вещественные данные). Поэтому при выводе применяются другие форматирующие атрибуты: ":7:2".
Так как алгоритм решения обеих задач не содержит ветвлений, для проверки его правильности достаточно небольшого числа тестовых запусков. Мы установили это число равным трем, указав его в качестве параметра процедуры SetTestCount.
Нам осталось включить вызовы процедуры Exam1 (с параметрами, равными 1 и 2) в основную процедуру группы InitTask, связав эти вызовы с номерами заданий. Следует разместить новые задания сразу после импортированных заданий ExamBegin71 и ExamBegin72, так как все эти задания относятся к одной и той же подгруппе "Преобразование массивов". При этом номера последних 12 заданий увеличатся на 2:
procedure InitTask(num: integer);
begin
case num of
1..2: UseTask('ExamBegin', 70 + num);
3..4: Exam1(num - 2);
5..16: UseTask('ExamTaskC', 20 + num);
end;
end;
Необходимо также увеличить на 2 пятый параметр процедуры CreateGroup, определяющий общее количество заданий в группе (теперь это количество должно быть равно 16).
Для просмотра новых заданий в окне задачника надо заменить в параметре процедуры Task тестирующей программы символ "#" на "?": Task('ExamDemo?').
При нажатии клавиши [F9] мы увидим на экране окно задачника в демо-режиме, в котором можно выбрать и просмотреть все задания, включенные к настоящему моменту в нашу группу. Приведем вид окна для задания ExamDemo4 (напомним, что это задание инициализируется посредством вызова процедуры Exam1 с параметром, равным 2):
Добавление заданий повышенной сложностиНаша группа ExamDemo к настоящему моменту содержит 12 заданий повышенной сложности, импортированных из группы ExamTaskC. Все эти задания связаны с общей предметной областью; они содержат сведения об абитуриентах и включают их фамилии, номера школ и годы поступления в вузы. Для того чтобы проиллюстрировать некоторые особенности, связанные с разработкой подобных заданий, дополним набор уже имеющихся заданий двумя новыми заданиями из той же предметной области.
Новые задания будут связаны с группировкой абитуриентов по школам: для каждой школы надо найти связанный с ней минимальный (или максимальный) год поступления абитуриента. Второе из двух заданий мы усложним, дополнительно потребовав, чтобы полученные результаты были отсортированы по убыванию максимального года (а для одинаковых годов -- по возрастанию номера школы). Первое задание сделаем более простым: в нем результирующие данные надо располагать по возрастанию номеров школ.
При генерации наборов исходных данных нам потребуются не только числа (номера школ и годы поступления), но и строковые данные -- фамилии абитуриентов (хотя для выполнения этих заданий они не требуются). Проще всего определить массив возможных фамилий достаточно большого размера, из которого выбирать элементы случайным образом. Заметим, что в условии заданий не говорится о том, что все фамилии в исходном наборе должны быть различными, поэтому совпадения фамилий вполне допустимы (если в некоторой группе заданий все фамилии должны быть уникальными, то целесообразно дополнять их инициалами, чтобы обеспечить большее разнообразие; кроме того, для таких заданий при добавлении к набору исходных данных новой фамилии необходимо проверять, что среди уже имеющихся элементов набора отсутствует данная фамилия с теми же инициалами).
Добавим к нашей библиотеке вспомогательный массив фамилий из 40 элементов (обратите внимание на то, что по правилам языка PascalABC.NET между описанием массива и списком инициализирующих значений указывается знак присваивания):