686 lines
35 KiB
HTML
686 lines
35 KiB
HTML
<html>
|
||
|
||
<head>
|
||
<object type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e">
|
||
<param name="Keyword" value="">
|
||
</object>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
|
||
<title></title>
|
||
<link rel="StyleSheet" href="../../default.css">
|
||
</head>
|
||
|
||
<body>
|
||
<H1>Модуль PT4TaskMakerNET: разработка заданий, связанных с ЕГЭ по информатике</H1>
|
||
<hr>
|
||
|
||
<h2>Группы заданий Exam и их особенности</h2>
|
||
<p>Начиная с версии 4.10, в базовый набор задачника Programming Taskbook
|
||
для языков Pascal и C++ входят
|
||
специальные группы заданий, связанные с ЕГЭ по информатике: ExamBegin и ExamTaskC.
|
||
Порядок выполнения заданий из этих групп имеет ряд особенностей, основной из которых
|
||
является отказ от применения специальных средств ввода-вывода, входящих в состав
|
||
задачника. В заданиях групп Exam для ввода-вывода надо применять стандартные средства
|
||
используемого языка программирования. Это позволяет максимально приблизить вид программы,
|
||
выполняющей задание, к виду, требуемому на экзамене, а также учесть при выполнении задания
|
||
его дополнительные особенности, связанные с организацией ввода исходных данных и форматированием
|
||
результатов.
|
||
</p>
|
||
<p>С использованием конструктора учебных заданий PT4TaskMaker преподаватель может разрабатывать
|
||
новые группы заданий, связанные с ЕГЭ по информатике. При этом необходимо следовать
|
||
дополнительным правилам, основные из которых приводятся ниже.
|
||
</p>
|
||
<a name="list"></a>
|
||
<ol><li>Любые группы заданий, связанные с ЕГЭ по информатике, должны иметь имена, начинающиеся
|
||
с префикса Exam (для групп с этим префиксом задачник генерирует специальные программы-заготовки,
|
||
позволяющие использовать при выполнении задания стандартные средства ввода-вывода).
|
||
<li>Необходимо проверять номер текущей версии задачника и текущий язык программирования, создавая
|
||
новую группу тольков случае, если версия имеет номер не ниже 4.10, а языком программирования является
|
||
Pascal или C++.
|
||
<li>В преамбуле к группе заданий желательно отметить тот факт, что для ввода-вывода необходимо
|
||
использовать стандартные средства языка.
|
||
<li>В новые группы Exam следует импортировать только те задания, которые также относятся к группам Exam.
|
||
<li>Набор исходных и контрольных данных надо сохранять в текстовых файлах, передавая задачнику
|
||
информацию об именах этих файлов (процедурами DataS) и связывая содержимое этих файлов
|
||
с разделами исходных и результирующих данных (процедурами DataFileT и ResultFileT соответственно).
|
||
<li>При любых вариантах наборов исходных данных соответствующие контрольные файлы не должны быть пустыми
|
||
(при наличии пустого файла результатов задачник считает запуск программы ознакомительным).
|
||
</ol>
|
||
<p>Проиллюстрируем эти правила, разработав в среде PascalABC.NET демонстрационную группу заданий ExamDemo.
|
||
Задания, связанные с ЕГЭ, вполне допустимо разрабатывать и на других языках, поддерживаемых
|
||
конструктором учебных заданий, в частности, на языке C++ или на языке Pascal в средах Delphi или Lazarus,
|
||
причем полученные реализации не будут иметь никаких существенных отличий от реализации, приведенной ниже.
|
||
Задания можно разрабатывать даже на языке C#, несмотря на то что <i>выполнять</i> их на этом языке будет нельзя.
|
||
</p>
|
||
|
||
<h2>Реализация сводной группы заданий</h2>
|
||
<p>Напомним, что <i>сводной группой</i> называется группа, все задания которой импортированы из уже имеющихся групп.
|
||
Сводные группы оказываются очень полезными при составлении вариантов проверочных работ, поскольку
|
||
позволяют дать заданиям новые имена и тем самым затрудняют применение разного рода шпаргалок.
|
||
</p>
|
||
<p>Так как в сводных группах отсутствуют новые задания, при разработке сводных групп для заданий, связанных
|
||
с ЕГЭ по информатике, достаточно учесть правила 1–4, приведенные в предыдущем пункте.
|
||
</p>
|
||
<p>Будем предполагать, что общие правила разработки новых
|
||
групп заданий в среде PascalABC.NET читателю известны (см. раздел «<a href="tmaker_samples.html">Примеры</a>»).
|
||
</p>
|
||
<p>Следуя правилам именования групп (имя должно состоять из латинских букв и цифр, иметь длину не более 9
|
||
символов и не оканчиваться цифрой), а также правилу 1 из предыдущего пункта
|
||
(наличие префикса Exam),
|
||
назовем нашу группу ExamDemo. Динамическая библиотека в этом случае должна иметь имя PT4ExamDemo.
|
||
</p>
|
||
<p>Импортируем в группу ExamDemo несколько заданий из обеих групп Exam, входящих в базовый набор.
|
||
Из группы ExamBegin возьмем задания ExamBegin71 и ExamBegin72,
|
||
входящие в подгруппу «Преобразование массивов»
|
||
и связанные перестановкой элементов массива.
|
||
Из группы ExamTaskC возьмем серию из 12 заданий ExamTaskC25–ExamTaskC36,
|
||
объединенных общей предметной областью: сведениями об абитуриентах из различных школ.
|
||
</p>
|
||
<p>Учитывая правила подготовки динамических библиотек с группами учебных заданий,
|
||
а также правила 2 и 3 из предыдущего пункта, получим следующий вариант нашей библиотеки (файл PT4ExamDemo.pas):
|
||
</p>
|
||
<pre>library PT4ExamDemo;
|
||
|
||
uses PT4TaskMakerNET;
|
||
|
||
procedure InitTask(num: integer);
|
||
begin
|
||
case num of
|
||
1..2: UseTask('ExamBegin', 70 + num);
|
||
3..14: UseTask('ExamTaskC', 22 + num);
|
||
end;
|
||
end;
|
||
|
||
procedure inittaskgroup;
|
||
begin
|
||
if (CurrentVersion < '4.10') or
|
||
(CurrentLanguage and (lgPascal or lgCPP) = 0) then
|
||
exit;
|
||
CreateGroup('ExamDemo', '^ЕГЭ по информатике: примеры различных задач',
|
||
'М. Э. Абрамян, 2013', 'qdfedsag33gbg45j', 14, InitTask);
|
||
CommentText('\PПри выполнении заданий данной группы вместо');
|
||
CommentText('специальных операций ввода-вывода, предоставляемых');
|
||
CommentText('задачником, необходимо применять стандартные операции');
|
||
CommentText('используемого языка программирования: процедуры');
|
||
CommentText('\MRead\m/\MReadln\m\:\MWrite\m/\MWriteln\m для языка');
|
||
CommentText('Pascal, потоки \Mcin\m\:\Mcout\m для языка C++.');
|
||
end;
|
||
|
||
procedure activate(S: string);
|
||
begin
|
||
ActivateNET(S);
|
||
end;
|
||
|
||
begin
|
||
end.
|
||
</pre>
|
||
<p>Кратко опишем полученную программу. Вначале к ней подключается модуль PT4TaskMakerNET, в котором
|
||
реализован конструктор учебных заданий для среды PascalABC.NET.
|
||
Затем следует описание основной процедуры группы заданий
|
||
<a href="tmaker_proc1.html#TInitTaskProc">InitTask</a>, определяющей задание по его номеру.
|
||
Поскольку мы не создавали своих заданий, в данной процедуре
|
||
используется только стандартная процедура <a href="tmaker_proc1.html#UseTask">UseTask</a>, позволяющая
|
||
импортировать задания из имеющихся групп.
|
||
В нашем случае импортируются задания с номерами 71 и 72 из группы ExamBegin и задания с номерами 25–36
|
||
из группы ExamTaskC (всего 14 заданий).
|
||
</p>
|
||
<p>Затем описывается процедура инициализации данной группы заданий. Она имеет стандартное имя inittaskgroup
|
||
(набранное строчными, т. е. маленькими буквами). В этой процедуре
|
||
вызывается процедура <a href="tmaker_proc1.html#CreateGroup">CreateGroup</a>, в которой задаются
|
||
характеристики создаваемой группы:
|
||
имя (<code>'ExamDemo'</code>), описание (<code>'^ЕГЭ по информатике: примеры различных задач'</code>), сведения об авторе,
|
||
строковый ключ, число заданий (<code>14</code>) и основная процедура группы (<code>InitTask</code>).
|
||
</p>
|
||
<p>Поскольку надо гарантировать, что группа будет создана только в случае использования задачника версии не ниже 4.10
|
||
и только для языков Pascal и С++, перед вызовом процедуры инициализации группы CreateGroup выполняется проверка
|
||
перечисленных выше условий. Если хотя бы одно из условий нарушено, то выполняется немедленный выход из
|
||
процедуры inittaskgroup, и группа ExamDemo не создается.
|
||
</p>
|
||
<p>Следует обратить внимание на наличие символа-метки «^» в начале строки-описания группы.
|
||
Этот символ отменяет автоматическое преобразование к нижнему регистру
|
||
первой буквы описания при его выводе в программных модулях PT4Demo и PT4Load (если бы символ «^»
|
||
отсутствовал, то строка с описанием данной группы имела бы вид «Тема: еГЭ по информатике:
|
||
примеры различных задач»).
|
||
</p>
|
||
<p>Наконец, в соответствии с правилом 3, мы включили в создаваемую группу <i>преамбулу</i>,
|
||
в которой отмечается основная особенность данной группы: необходимость использования
|
||
стандартных средств ввода-вывода (для определения текста преамбулы вызываются
|
||
процедуры <a href="tmaker_proc1.html#CommentText">CommentText</a>). Обратите внимание на управляющие
|
||
последовательности <code>\M</code>–<code>\m</code>, позволяющие отобразить фрагмент текста <code>моноширинным шрифтом</code>.
|
||
</p>
|
||
<p>После процедуры inittaskgroup описывается вспомогательная процедура activate
|
||
(ее имя также должно быть набрано строчными буквами), в которой необходимо вызвать
|
||
процедуру ActivateNET, описанную в модуле PT4TaskMakerNET.
|
||
</p>
|
||
<p>Для успешной компиляции программы с созданной группой необходимо, чтобы ей был доступен
|
||
модуль PT4TaskMakerNET. Этот модуль входит в число стандартных модулей библиотеки системы PascalABC.NET
|
||
и размещается в подкаталоге LIB системного каталога PascalABC.NET, поэтому копировать его в рабочий каталог
|
||
не требуется.
|
||
</p>
|
||
<p>Для того чтобы при успешной компиляции можно было сразу просмотреть содержимое созданной группы,
|
||
достаточно использовать вспомогательную тестирующую программу, являющуюся заготовкой для выполнения
|
||
заданий из созданной группы.
|
||
Эту программу проще всего создать с помощью программного модуля PT4Load,
|
||
нажав комбинацию клавиш [Shift]+[Ctrl]+[L] и введя в поле «Задание»; имя первого задания
|
||
группы: ExamDemo1. В результате будет создан файл с именем ExamDemo1.pas со следующим содержимым:
|
||
</p>
|
||
<pre>uses PT4Exam;
|
||
|
||
begin
|
||
Task('ExamDemo1');
|
||
|
||
end.
|
||
</pre>
|
||
<p>Следует обратить внимание на то, что к созданной программе подключается не традиционный модуль
|
||
задачника PT4, а специальный его вариант PT4Exam, используемый для заданий, связанных с ЕГЭ.
|
||
</p>
|
||
<p>Для того чтобы тестирующая программа отображала задания в демонстрационном режиме, причем
|
||
при ее запуске на экране отображалось последнее из заданий, включенных в группу, достаточно заменить
|
||
в процедуре Task номер задания на символ «?»: <code>Task('ExamDemo?')</code>.
|
||
</p>
|
||
<p>Теперь при запуске тестирующей программы на экране отобразится окно задачника с заданием ExamDemo14:
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4exam1a.png">
|
||
</p>
|
||
<p>По умолчанию окно задачника отображается в режиме с динамической компоновкой, который появился
|
||
в версии 4.11 и является более наглядным, чем режим с фиксированной компоновкой. Однако
|
||
при разработке заданий желательно применять режим с фиксированной компоновкой, поскольку
|
||
он позволит выявить недостатки форматирования (в частности, вертикального выравнивания данных),
|
||
присущие только этому режиму. Для переключения между режимами отображения данных достаточно нажать клавишу
|
||
[F4]. После выполнения этого действия окно задачника изменится следующим образом:
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4exam1.png">
|
||
</p>
|
||
|
||
<p>В окне задачника можно просматривать все имеющиеся задания данной группы
|
||
(нажимая клавиши [Enter] и [Backspace], а также генерировать различные варианты исходных данных
|
||
и связанных с ними контрольных (т. е. «правильных») результатов. При закрытии окна
|
||
программа немедленно завершит работу, и мы вернемся в редактор среды PascalABC.NET.
|
||
Заметим, что при последующих запусках программы будет автоматически выбираться тот режим окна
|
||
задачника, в котором оно находилось в момент его предшествующего закрытия.
|
||
</p>
|
||
<p>Для того чтобы cгенерировать html-страницу с описанием созданной группы (это позволяет, в частности,
|
||
увидеть текст преамбулы группы), достаточно в процедуре Task тестирующей программы заменить символ «?»
|
||
на символ «#»: <code>Task('ExamDemo#')</code>.
|
||
Теперь при запуске этой программы на экране вместо окна задачника с заданием ExamDemo14
|
||
появится html-браузер с описанием созданной группы:
|
||
</p>
|
||
|
||
<p class="ris"><img border="1" src="images\pt4exam2.png">
|
||
</p>
|
||
|
||
<h2>Добавление новых заданий</h2>
|
||
<p>Добавим к нашей группе новые задания. Подобно заданиям, импортированным
|
||
из группы ExamBegin, они будут посвящены преобразованию массивов путем перестановки их элементов.
|
||
Если импортированные задания были посвящены инвертированию массива (или его части), то в новых
|
||
заданиях надо будет выполнить перестановку всех пар элементов или перестановку первой и второй
|
||
половины массива. Чтобы не уточнять действия в случае массивов нечетного размера,
|
||
добавим в задание условие о том, что исходный массив всегда имеет четный размер.
|
||
Тип элементов массива для подобных заданий является несущественным, поэтому будем обрабатывать
|
||
массивы вещественных чисел. Таким образом, набор исходных данных будет иметь вид, подобный
|
||
набору из задания ExamDemo2 (см. формулировку этого задания, приведенную на предыдущем рисунке).
|
||
Оформление вывода результатов также не будет отличаться от
|
||
оформления, требуемого в задании ExamDemo2.
|
||
</p>
|
||
<p>Прежде чем приступать к реализации заданий, добавим в начало нашей библиотеки
|
||
описание ряда вспомогательных переменных, процедур и функций.
|
||
</p>
|
||
<p>Первая функция упрощает генерацию случайных исходных данных вещественного типа
|
||
с не более чем двумя дробными знаками:
|
||
</p>
|
||
<pre>function RandR(a, b: integer): real;
|
||
begin
|
||
result := RandomN(a*100, b*100)/100;
|
||
end;
|
||
</pre>
|
||
<p>При реализации функции RandR мы использовали функцию <a href="tmaker_proc1.html#Random">RandomN</a>, входящую в состав
|
||
конструктора учебных заданий, начиная с версии 4.11 (функция RandomN(M, N) возвращает
|
||
случайное целое число, лежащее в диапазоне M..N, включая границы диапазона). Функция
|
||
RandR(a, b) (a и b — целые) возвращает вещественное число, лежащее в диапазоне a..b
|
||
и <i>имеющее не более двух дробных знаков</i>. Такие числа можно без потери точности
|
||
записывать в текстовый файл в формате с двумя дробными
|
||
знаками; таким образом, программа учащегося прочтет из файла именно то число,
|
||
которое было сгенерировано при инициализации задания. Напомним, что в версии 4.11 конструктора
|
||
имеется функция RandomR, также предназначенная для генерации случайных вещественных чисел, однако она
|
||
не позволяет фиксировать число дробных знаков, и поэтому менее пригодна для генерации
|
||
данных, предназначенных для записи в текстовые файлы.
|
||
</p>
|
||
<p>Поскольку в обоих заданиях нам потребуется выполнять обмен значений, содержащихся в вещественных переменных,
|
||
опишем процедуру, которая выполняет подобный обмен:
|
||
</p>
|
||
<pre>procedure SwapR(var a, b: real);
|
||
var
|
||
c: real;
|
||
begin
|
||
c := a;
|
||
a := b;
|
||
b := c;
|
||
end;
|
||
</pre>
|
||
<p>Следующая группа вспомогательных переменных и процедур связана с особенностью заданий типа Exam, описанной
|
||
в правиле 5 (см. первый пункт данного раздела). Ввиду важности этого правила приведем его еще раз:
|
||
</p>
|
||
<ul><li>Набор исходных и контрольных данных надо сохранять в текстовых файлах, передавая задачнику
|
||
информацию об именах этих файлов (процедурами DataS) и связывая содержимое этих файлов
|
||
с разделами исходных и результирующих данных (процедурами DataFileT и ResultFileT соответственно).
|
||
</ul>
|
||
<p>Таким образом, при инициализации каждого задания группы Exam надо выполнить следующие действия:
|
||
</p>
|
||
<ol><li>Сгенерировать имена файлов, содержащих исходные и контрольные данные (эти имена должны быть различными
|
||
и меняться при каждом тестовом испытании программы; кроме того, подобно всем файлам, используемым в заданиях,
|
||
они должны иметь расширение .tst).
|
||
<li>Связать созданные имена с файловыми переменными и открыть эти файлы на запись.
|
||
<li>Заполнить файлы необходимыми данными.
|
||
<li>Закрыть файлы с исходными и контрольными данными.
|
||
<li>Передать задачнику информацию об именах созданных файлов, чтобы при выполнении задания эта
|
||
информация была использована при связывании файлов со стандартными потоками ввода-вывода.
|
||
<li>Передать задачнику информацию о том, что первый из созданных файлов должен
|
||
быть включен в раздел исходных данных, а второй — в раздел результатов; это, во-первых, позволит
|
||
отобразить содержимое файлов в окне задачника и, во-вторых, обеспечит проверку правильности
|
||
результирующего файла, созданного программой учащегося (путем его сравнения с данными контрольного файла).
|
||
</ol>
|
||
<p>От условий конкретного задания будет зависеть только действие 3, связанное с заполнением файлов нужными данными.
|
||
Все остальные действия являются стандартными и должны выполняться при инициализации любого задания групп Exam.
|
||
Поэтому удобно оформить эти действия в виде двух вспомогательных процедур, одна из которых (StartExam) выполняет
|
||
начальные действия 1–2, а другая (EndExam) — завершающие действия 4–6. Поскольку в каждой из этих
|
||
процедур необходимо использовать имена созданных файлов и связанные с ними файловые переменные, эти переменные
|
||
удобно описать как глобальные:
|
||
</p>
|
||
<pre>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;
|
||
</pre>
|
||
<p>Обсудим особенности этих процедур. Имена файлов, создаваемых в процедуре StartExam,
|
||
имеют вид pt1#####.tst (для файла с исходными
|
||
данными) и pt2#####.tst (для файла с контрольными данными), причем в позициях, помеченных символом «#»,
|
||
располагаются цифры, выбираемые случайным образом. Тем самым обеспечиваются все требования к именам файлов:
|
||
они генерируются случайным образом, имеют расширение .tst, и имя файла с исходными данными всегда отличается
|
||
от имени контрольного файла. Напомним, что все файлы с расширением .tst автоматически удаляются из рабочего каталога
|
||
после проверки учебного задания.
|
||
</p>
|
||
<p>При анализе процедуры EndExam следует обратить внимание на то, что информация о содержимом исходного файла
|
||
занимает всю область исходных данных (строки с первой по пятую — см. вызов процедуры
|
||
<a href="tmaker_proc2.html#DataFile">DataFileT</a>)
|
||
и, таким образом, она <i>скрывает</i>
|
||
информацию об именах файлов, ранее выведенную в первой строке области исходных данных (см. вызовы процедур
|
||
<a href="tmaker_proc1.html#DataN">DataS</a>).
|
||
В обычном задании такая реализация была бы ошибочной, поскольку учащийся не увидел бы
|
||
на экране имена файлов и не понял бы, что эти имена необходимо ввести и обработать в его программе.
|
||
Однако в задании групп Exam именно такая реализация является правильной, поскольку
|
||
ввод имен файлов и связывание этих файлов со стандартными потоками ввода-вывода выполняется
|
||
автоматически («незаметно» для программы учащегося), и поэтому информацию
|
||
об именах файлов на экране отображать не следует.
|
||
</p>
|
||
<p>Итак, наличие процедур StartExam и EndExam позволяет нам упростить
|
||
реализацию заданий: после определения формулировки любого задания нам достаточно
|
||
вызвать процедуру StartExam, заполнить файлы f1 и f2 исходными и, соответственно,
|
||
контрольными данными и вызвать процедуру EndExam.
|
||
</p>
|
||
<p>Приступим к непосредственной реализации заданий. Поскольку эти задания являются однотипными,
|
||
реализуем их в одной процедуре Exam1, снабдив ее параметром m: при m = 1 будет инициализироваться
|
||
первое задание, а при m = 2 — второе:
|
||
</p>
|
||
<pre>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;
|
||
</pre>
|
||
<p>Обратите внимание на то, что при вызове процедуры CreateTask ей передается строковый параметр, содержащий
|
||
имя подгруппы «Преобразование массивов». Это обеспечивает включение новых заданий
|
||
в подгруппу, с которой связаны ранее импортированные в нашу группу задания ExamBegin71 и ExamBegin72.
|
||
</p>
|
||
<p>Размер исходного массива всегда будет четным и не превосходящим 10; последнее условие необходимо для
|
||
того, чтобы все исходные данные можно было разместить на одной экранной строке.
|
||
</p>
|
||
<p>В обоих заданиях значения элементов исходного массива можно выбирать произвольным образом из некоторого диапазона.
|
||
Мы выбрали диапазон от -99 до 99, поскольку в этом случае при отображении чисел с двумя дробными знаками
|
||
они будут занимать не более 6 экранных позиций.
|
||
</p>
|
||
<p>При записи в файл элементов исходного массива между ними всегда располагается по одному пробелу, поскольку
|
||
такой порядок организации исходных данных принят во всех заданиях групп ExamBegin и ExamTaskC. Чтобы обеспечить
|
||
при этом отображение вещественных чисел с двумя дробными знаками, используется специальный набор форматирующих
|
||
атрибутов: «<code>:0:2</code>». При выводе результатов, согласно формулировке задания,
|
||
необходимо отводить для каждого элемента массива по 7 экранных позиций и выводить его с двумя дробными знаками
|
||
(последнее условие принято по умолчанию во всех заданиях групп ExamBegin и ExamTaskC, использующих
|
||
вещественные данные). Поэтому при выводе применяются другие форматирующие атрибуты: «<code>:7:2</code>».
|
||
</p>
|
||
<p>Так как алгоритм решения обеих задач не содержит ветвлений, для проверки его правильности
|
||
достаточно небольшого числа тестовых запусков. Мы установили это число равным трем, указав его
|
||
в качестве параметра процедуры <a href="tmaker_proc1.html#SetTestCount">SetTestCount</a>.
|
||
</p>
|
||
<p>Нам осталось включить вызовы процедуры Exam1 (с параметрами, равными 1 и 2)
|
||
в основную процедуру группы InitTask, связав эти вызовы с номерами заданий. Следует разместить новые
|
||
задания сразу после импортированных заданий ExamBegin71 и ExamBegin72, так как все эти задания
|
||
относятся к одной и той же подгруппе «Преобразование массивов». При этом номера
|
||
последних 12 заданий увеличатся на 2:
|
||
</p>
|
||
<pre>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;
|
||
</pre>
|
||
<p>Необходимо также увеличить на 2 пятый параметр процедуры CreateGroup, определяющий общее количество
|
||
заданий в группе (теперь это количество должно быть равно 16).
|
||
</p>
|
||
<p>
|
||
Для просмотра новых заданий в окне задачника надо заменить в параметре процедуры Task
|
||
тестирующей программы символ «#» на «?»: <code>Task('ExamDemo?')</code>.
|
||
</p>
|
||
<p>При нажатии клавиши [F9] мы увидим на экране окно задачника в демо-режиме, в котором можно выбрать
|
||
и просмотреть все задания, включенные к настоящему моменту в нашу группу. Приведем вид окна для задания ExamDemo4
|
||
(напомним, что это задание инициализируется посредством вызова процедуры Exam1 с параметром, равным 2):
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4exam3.png">
|
||
</p>
|
||
|
||
<h2>Добавление заданий повышенной сложности</h2>
|
||
<p>Наша группа ExamDemo к настоящему моменту содержит 12 заданий повышенной сложности, импортированных
|
||
из группы ExamTaskC. Все эти задания связаны с общей предметной областью; они содержат сведения об
|
||
абитуриентах и включают их фамилии, номера школ и годы поступления в вузы. Для того чтобы проиллюстрировать
|
||
некоторые особенности, связанные с разработкой подобных заданий, дополним набор уже имеющихся заданий двумя
|
||
новыми заданиями из той же предметной области.
|
||
</p>
|
||
<p>Новые задания будут связаны с группировкой абитуриентов по школам:
|
||
для каждой школы надо найти связанный с ней минимальный (или максимальный) год
|
||
поступления абитуриента. Второе из двух заданий мы усложним, дополнительно потребовав, чтобы
|
||
полученные результаты были отсортированы по убыванию максимального года (а для одинаковых годов —
|
||
по возрастанию номера школы). Первое задание сделаем более простым: в нем результирующие данные
|
||
надо располагать по возрастанию номеров школ.
|
||
</p>
|
||
<p>При генерации наборов исходных данных нам потребуются не только числа (номера школ и годы поступления),
|
||
но и строковые данные — фамилии абитуриентов (хотя для выполнения этих заданий они не требуются).
|
||
Проще всего определить массив возможных фамилий достаточно большого размера, из которого выбирать
|
||
элементы случайным образом. Заметим, что в условии заданий не говорится о том, что все фамилии
|
||
в исходном наборе должны быть различными, поэтому совпадения фамилий вполне допустимы (если
|
||
в некоторой группе заданий все фамилии должны быть уникальными, то целесообразно дополнять их <i>инициалами</i>,
|
||
чтобы обеспечить большее разнообразие; кроме того, для таких заданий
|
||
при добавлении к набору исходных данных новой фамилии необходимо проверять,
|
||
что среди уже имеющихся элементов набора отсутствует данная фамилия с теми же инициалами).
|
||
</p>
|
||
<p>Добавим к нашей библиотеке вспомогательный массив фамилий из 40 элементов
|
||
(обратите внимание на то, что по правилам языка PascalABC.NET между описанием массива
|
||
и списком инициализирующих значений указывается знак присваивания):
|
||
</p>
|
||
<pre>const
|
||
famcount = 40;
|
||
var
|
||
fam: array[1..famcount] of string :=
|
||
('Иванов', 'Петров', 'Сидоров', 'Кузнецов', 'Филиппов',
|
||
'Сергеев', 'Александров', 'Петухов', 'Пономарев', 'Яшин',
|
||
'Греков', 'Иванова', 'Кузнецова', 'Алексеева', 'Зайцев',
|
||
'Волкова', 'Фролов', 'Юрьев', 'Бондарев', 'Семенов',
|
||
'Семенова', 'Федченко', 'Марченко', 'Борисова', 'Петровский',
|
||
'Беляева', 'Белкин', 'Лысенко', 'Сорокина', 'Пастухов',
|
||
'Юрьева', 'Кондратьев', 'Тимофеев', 'Степанова', 'Якимов',
|
||
'Юсов', 'Степанов', 'Руденко', 'Демидов', 'Леонидов');
|
||
</pre>
|
||
<p>Оба новых задания, как и два предыдущих,
|
||
мы реализуем в виде одной процедуры с параметром m, принимающим значения 1 или 2:
|
||
</p>
|
||
<pre>procedure Exam2(m: integer);
|
||
var
|
||
n, i, y, num, max, k: integer;
|
||
a: array[1..100] of integer;
|
||
nums: array[1..10] of integer;
|
||
begin
|
||
CreateTask('Обработка сложных наборов данных');
|
||
case m of
|
||
1:
|
||
begin
|
||
TaskText('На вход подаются сведения об абитуриентах. В первой строке указывается',0,1);
|
||
TaskText('количество абитуриентов {N}, каждая из последующих {N} строк имеет формат',0,2);
|
||
TaskText('\(\M<Год поступления> <Фамилия> <Номер школы>\m\)',0,3);
|
||
TaskText('Номер школы содержит не более двух цифр, годы лежат в диапазоне от 1990',0,4);
|
||
TaskText('до 2010. Для каждого номера школы, присутствующего в исходных данных,',0,5);
|
||
TaskText('определить связанный с ним минимальный год поступления (вначале указывать',0,0);
|
||
TaskText('номер школы, затем минимальный год). Сведения о каждой школе выводить',0,0);
|
||
TaskText('на новой строке и упорядочивать по возрастанию номера школы.',0,0);
|
||
k := 1;
|
||
for i := 1 to 100 do
|
||
a[i] := 2100;
|
||
end;
|
||
2:
|
||
begin
|
||
TaskText('На вход подаются сведения об абитуриентах. В первой строке указывается',0,1);
|
||
TaskText('количество абитуриентов {N}, каждая из последующих {N} строк имеет формат',0,2);
|
||
TaskText('\(\M<Номер школы> <Фамилия> <Год поступления>\m\)',0,3);
|
||
TaskText('Номер школы содержит не более двух цифр, годы лежат в диапазоне от 1990',0,4);
|
||
TaskText('до 2010. Для каждого номера школы, присутствующего в исходных данных,',0,5);
|
||
TaskText('определить связанный с ним максимальный год поступления (вначале указывать',0,0);
|
||
TaskText('максимальный год, затем номер школы). Сведения о каждой школе выводить',0,0);
|
||
TaskText('на новой строке и упорядочивать по убыванию максимального года,',0,0);
|
||
TaskText('а для совпадающих годов \= по возрастанию номера школы.',0,0);
|
||
k := -1;
|
||
for i := 1 to 100 do
|
||
a[i] := 0;
|
||
end;
|
||
end;
|
||
StartExam;
|
||
if Random(2)=0 then
|
||
n := RandomN(50, 100)
|
||
else
|
||
n := RandomN(10, 20);
|
||
case CurrentTest of
|
||
1: n := RandomN(10, 20);
|
||
2: n := RandomN(50, 100);
|
||
end;
|
||
if n <= 20 then
|
||
for i := 1 to 10 do
|
||
nums[i] := RandomN(1, 100);
|
||
writeln(f1,n);
|
||
for i := 1 to n do
|
||
begin
|
||
y := RandomN(1990, 2010);
|
||
if n <= 20 then
|
||
num := nums[RandomN(1, 10)]
|
||
else
|
||
num := RandomN(1, 100);
|
||
case m of
|
||
1: writeln(f1, y, ' ', fam[RandomN(1, famcount)],' ', num);
|
||
2: writeln(f1, num, ' ', fam[RandomN(1, famcount)],' ', y);
|
||
end;
|
||
if k*a[num] > k*y then
|
||
a[num] := y;
|
||
end;
|
||
case m of
|
||
1: for i := 1 to 100 do
|
||
if a[i] < 2100 then
|
||
writeln(f2, i, ' ', a[i]);
|
||
2: while true do
|
||
begin
|
||
max := 0;
|
||
for i := 1 to 100 do
|
||
if a[i] > max then
|
||
begin
|
||
max := a[i];
|
||
num := i;
|
||
end;
|
||
if max = 0 then
|
||
break
|
||
else
|
||
begin
|
||
writeln(f2, max, ' ', num);
|
||
a[num] := 0;
|
||
end;
|
||
end;
|
||
end;
|
||
EndExam;
|
||
SetTestCount(5);
|
||
end;
|
||
</pre>
|
||
<p>Обсудим детали реализации этих заданий. Начальная часть их формулировки
|
||
посвящена описанию предметной области и является стандартной для данной серии заданий. Обратите внимание на то, что
|
||
поля исходных записей в заданиях указываются в различном порядке.
|
||
Этот прием используется во всех сериях группы ExamTaskC, чтобы обеспечить большее разнообразие входящих в них задач.
|
||
</p>
|
||
<p>При определении завершающей части формулировки заданий последний параметр процедур TaskText полагается равным 0.
|
||
Это означает, что в режиме окна с фиксированной компоновкой данные строки
|
||
при первоначальном отображении задания не видны на экране, однако их можно просмотреть, используя
|
||
<i>прокрутку</i> раздела с формулировкой задания. В режиме с динамической компоновкой полный текст формулировки задания
|
||
сразу отображается на экране.
|
||
</p>
|
||
<p>В процедуре используются два массива: массив a предназначен для хранения контрольных (правильных) результатов,
|
||
а массив nums является вспомогательным (его назначение описывается далее).
|
||
Для хранения исходных данных массив не предусматривается, поскольку
|
||
после генерации полей очередной записи они будут немедленно записываться
|
||
в исходный файл и обрабатываться.
|
||
</p>
|
||
<p>Количество записей в исходном наборе данных записывается в переменную n.
|
||
Наборы, содержащие небольшое число записей, удобны при отладке программы (благодаря своей «обозримости»),
|
||
в то время как большие наборы позволяют проверить программу в «реальной» ситуации и тем самым
|
||
окончательно убедиться в правильности алгоритма.
|
||
Используя функцию <a href="tmaker_proc1.html#CurrentTest">CurrentTest</a>, добавленную в версию 4.11 конструктора
|
||
PT4TaskMaker, мы обеспечили дополнительную «настройку» процесса генерации исходных данных:
|
||
при первом тестовом запуске
|
||
программы с решением задачи ей всегда предлагается набор из небольшого количества записей (что упрощает поиск
|
||
и исправление ошибок), а при втором тестовом запуске — большой набор записей (что позволяет проверить предложенный
|
||
алгоритм «на прочность»). При последующих тестовых запусках (а также при демонстрационном
|
||
и ознакомительном запуске программы) значение n с равной вероятностью
|
||
выбирается либо из диапазона 10..20, либо из диапазона 50..100.
|
||
</p>
|
||
<p>В случае генерации исходных данных для указанных заданий при небольших значениях n (10–20)
|
||
возникает дополнительная проблема:
|
||
если выбирать случайным образом номера школ из всего допустимого диапазона 1–100,
|
||
то с большой вероятностью каждый номер школы появится в наборе исходных данных
|
||
всего по одному разу, что не позволит проверить правильность реализованного в программе алгоритма нахождения
|
||
минимального/максимального значения. Чтобы решить эту проблему, используется вспомогательный массив nums из 10
|
||
элементов, в который заносятся 10 случайно выбранных номеров школ,
|
||
после чего номера школ для исходного набора записей выбираются уже из этого набора номеров.
|
||
</p>
|
||
<p>В любом задании, связанном с нахождением набора записей, обычно требуется <i>отсортировать</i> полученный набор.
|
||
Задание должно быть сформулировано таким образом, чтобы обеспечить
|
||
<i>однозначный</i> порядок вывода полученных данных. В частности, если поле, по которому выполняется сортировка
|
||
(<i>главный ключ сортировки</i>),
|
||
может содержать одинаковые значения, то обязательно следует указать дополнительное поле
|
||
(<i>подчиненный ключ сортировки</i>),
|
||
по которому надо сортировать записи с одинаковым главным ключом.
|
||
При выводе отсортированных данных вначале надо располагать главный ключ,
|
||
после него — подчиненные ключи (если они имеются), затем — остальные поля
|
||
(такой порядок вывода принят во всех заданиях группы ExamTaskC).
|
||
</p>
|
||
<p>Для упорядочивания результатов во втором задании вместо сортировки массива a по убыванию
|
||
используется другой алгоритм, связанный с последовательным нахождением максимального элемента, выводом
|
||
этого элемента и его «порчей» (заменой его значения на 0). Обычная сортировка массива
|
||
в данном случае не позволит получить требуемый набор данных, так как при перемене местами значений
|
||
элементов в массиве a будет потеряна связь с номером школы (который определяется по индексу элемента).
|
||
Заметим, что возможен и вариант получения упорядоченного набора данных с помощью сортировки,
|
||
однако для этого надо использовать массив <i>записей</i>, полями которых являются максимальный год и номер школы.
|
||
</p>
|
||
<p>Во втором задании результаты должны упорядочиваться по <i>набору ключей</i>: первый
|
||
(главный) ключ — максимальный год (сортируется по убыванию), второй (подчиненный) ключ — номер школы
|
||
(сортируется по возрастанию).
|
||
Использованный нами способ упорядочивания обеспечивает <i>автоматическую</i> сортировку
|
||
по подчиненному ключу, так как при поиске очередного максимума массив a перебирается
|
||
по <i>возрастанию</i> индексов, и поэтому в результате находится номер <i>первого</i> максимального элемента.
|
||
</p>
|
||
<p>Прочие фрагменты процедуры Exam2 дополнительных комментариев не требуют.
|
||
</p>
|
||
<p>Вызов процедуры Exam2 надо добавить в конец оператора case процедуры InitTask, связав его
|
||
с номерами 17 и 18:
|
||
</p>
|
||
|
||
<pre>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);
|
||
17..18: Exam2(num - 16);
|
||
end;
|
||
end;
|
||
</pre>
|
||
<p>Кроме того, необходимо опять откорректировать параметр процедуры CreateGroup, определяющий количество
|
||
заданий, положив его равным 18.
|
||
</p>
|
||
<p>При нажатии клавиши [F9] на экране появится окно задачника с последним заданием данной группы
|
||
(на рисунке приведен вид окна после прокрутки раздела с формулировкой задания):
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4exam4.png">
|
||
</p>
|
||
|
||
|
||
<p>Если теперь опять заменить символ «?» на символ «# »
|
||
в параметре процедуры Task тестирующей программы,
|
||
то при нажатии [F9] мы увидим html-страницу с описанием группы, которое теперь содержит
|
||
не только импортированные, но и реализованные нами задания:
|
||
</p>
|
||
|
||
<p class="ris"><img border="1" src="images\pt4exam5.png">
|
||
</p>
|
||
<p>Поскольку при создании новых заданий мы указали в качестве параметра процедур CreateTask
|
||
названия подгрупп (и разместили новые задания после заданий из данных подгрупп),
|
||
новые задания отображаются в составе этих подгрупп: ExamDemo3 и ExamDemo4 — в подгруппе
|
||
«Преобразование массивов», а ExamDemo17 и ExamDemo18 — в подгруппе
|
||
«Обработка сложных наборов данных».
|
||
</p>
|
||
|
||
|
||
</body>
|
||
|
||
</html>
|
||
|