Programming Taskbook


E-mail:

Пароль:

Регистрация пользователя   Восстановление пароля

 

ЮФУ SMBU

Электронный задачник по программированию

©  М. Э. Абрамян (Южный федеральный университет, Университет МГУ-ППИ в Шэньчжэне), 1998–2024

 

Teacher Pack | Конструктор учебных заданий | Разработка заданий, связанных с ЕГЭ

PrevNext


Разработка групп заданий, связанных с ЕГЭ по информатике

Группы заданий Exam и их особенности

Начиная с версии 4.12 задачника Programming Taskbook, к нему можно подключать расширение: задачник для подготовки к ЕГЭ по информатике Programming Taskbook for Exam. Это расширение включает группы ExamBegin и ExamTaskC, каждая из которых содержит 100 учебных заданий. Порядок выполнения заданий из этих групп имеет ряд особенностей, основной из которых является отказ от применения специальных средств ввода-вывода, входящих в состав задачника. В заданиях групп Exam для ввода-вывода надо применять стандартные средства используемого языка программирования. Это позволяет максимально приблизить вид программы, выполняющей задание, к виду, требуемому на экзамене, а также учесть при выполнении задания его дополнительные особенности, связанные с организацией ввода исходных данных и форматированием результатов.

С использованием конструктора учебных заданий PT4TaskMaker преподаватель может разрабатывать новые группы заданий, связанные с ЕГЭ по информатике. При этом необходимо следовать дополнительным правилам, основные из которых приводятся ниже.

  1. Любые группы заданий, связанные с ЕГЭ по информатике, должны иметь имена, начинающиеся с префикса Exam (для групп с этим префиксом задачник генерирует специальные программы-заготовки, позволяющие использовать при выполнении задания стандартные средства ввода-вывода).
  2. Необходимо проверять номер текущей версии базового задачника и текущий язык программирования, создавая новую группу только в случае, если версия имеет номер не ниже 4.12, а языком программирования является язык, для которого реализовано расширение PT for Exam (для версии PT for Exam 1.2, соответствующей версии 4.14 базового задачника Programming Taskbook, такими языками являются Delphi Pascal, Free Pascal Lazarus, PascalABC.NET, C++ и Python 3).
  3. В преамбуле к группе заданий желательно отметить тот факт, что для ввода-вывода необходимо использовать стандартные средства языка программирования.
  4. В новые группы Exam следует импортировать только те задания, которые также относятся к группам Exam.
  5. Набор исходных и контрольных данных надо сохранять в текстовых файлах, передавая задачнику информацию об именах этих файлов (процедурами DataS) и связывая содержимое этих файлов с разделами исходных и результирующих данных (процедурами DataFileT и ResultFileT соответственно).
  6. При любых вариантах наборов исходных данных соответствующие контрольные файлы не должны быть пустыми (при наличии пустого файла результатов задачник считает запуск программы ознакомительным).

Проиллюстрируем эти правила, разработав демонстрационную группу заданий ExamDemo. Будем использовать вариант конструктора учебных заданий версии 4.14 для языка Pascal (среда Delphi или Lazarus). Задания, связанные с ЕГЭ, вполне допустимо разрабатывать и на других языках, поддерживаемых конструктором учебных заданий, в частности, на языке C++ или на языке Pascal в среде PascalABC.NET, причем полученные реализации не будут иметь никаких существенных отличий от реализации, приведенной ниже. Задания можно разрабатывать даже на языке C#, несмотря на то что выполнять их на этом языке будет нельзя.

Реализация сводной группы заданий

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

Так как в сводных группах отсутствуют новые задания, при разработке сводных групп для заданий, связанных с ЕГЭ по информатике, достаточно учесть правила 1–4, приведенные в предыдущем пункте.

Будем предполагать, что проект создается в среде Borland Delphi и что общие правила разработки новых групп заданий читателю известны (см. раздел «Примеры»).

Следуя правилам именования групп (имя должно состоять из латинских букв и цифр, иметь длину не более 9 символов и не оканчиваться цифрой), а также правилу 1 из предыдущего пункта (наличие префикса Exam), назовем нашу группу ExamDemo. Динамическая библиотека в этом случае должна иметь имя PT4ExamDemo.

Импортируем в группу ExamDemo несколько заданий из обеих групп Exam, входящих в базовый набор. Из группы ExamBegin возьмем задания ExamBegin71 и ExamBegin72, входящие в подгруппу «Преобразование массивов» и связанные перестановкой элементов массива. Из группы ExamTaskC возьмем серию из 12 заданий ExamTaskC25–ExamTaskC36, объединенных общей предметной областью: сведениями об абитуриентах из различных школ.

Учитывая правила подготовки динамических библиотек с группами учебных заданий, а также правила 2 и 3 из предыдущего пункта, получим следующий вариант нашей библиотеки (файл PT4ExamDemo.dpr):

{$R+,H+}
library PT4ExamDemo;

uses PT4TaskMaker;

procedure InitTask(num: integer); stdcall;
begin
  case num of
  1..2:  UseTask('ExamBegin', 70 + num);
  3..14: UseTask('ExamTaskC', 22 + num);
  end;
end;

procedure inittaskgroup;
begin
  if (CurrentVersion < '4.12') or
     (CurrentVersion < '4.14') and
       (CurrentLanguage and (lgPascal or lgCpp) = 0) or
     (CurrentLanguage and (lgPascal or lgCpp or lgPython3_flag) = 0) then
    exit;
  CreateGroup('ExamDemo', '^ЕГЭ по информатике: примеры различных задач',
    'М. Э. Абрамян, 2015', 'qdfedsag33gbg45j', 14, InitTask);
  CommentText('\PПри выполнении заданий данной группы вместо');
  CommentText('специальных операций ввода-вывода, предоставляемых');
  CommentText('задачником, необходимо применять стандартные операции');
  CommentText('используемого языка программирования: процедуры');
  CommentText('\MRead\m/\MReadln\m\:\MWrite\m/\MWriteln\m для языка');
  CommentText('Pascal, потоки \Mcin\m\:\Mcout\m для языка C++,');
  CommentText('функции \Minput\m и \Mprint\m для языка Python 3.');
end;

exports inittaskgroup;

begin
end.

Кратко опишем полученную программу. Вначале к ней подключается модуль PT4TaskMaker. Затем следует описание основной процедуры группы заданий InitTask, определяющей задание по его номеру. Поскольку мы не создавали своих заданий, в данной процедуре используется только стандартная процедура UseTask, позволяющая импортировать задания из имеющихся групп. В нашем случае импортируются задания с номерами 71 и 72 из группы ExamBegin и задания с номерами 25–36 из группы ExamTaskC (всего 14 заданий).

Затем описывается процедура инициализации данной группы заданий. Она имеет стандартное имя inittaskgroup (набранное строчными, т. е. маленькими буквами) и должна включаться в список exports процедур, экспортируемых данной библиотекой. В этой процедуре вызывается процедура CreateGroup, в которой задаются характеристики создаваемой группы: имя ('ExamDemo'), описание ('^ЕГЭ по информатике: примеры различных задач'), сведения об авторе, строковый ключ, число заданий (14) и основная процедура группы (InitTask).

Поскольку надо гарантировать, что группа будет создана только в случае использования задачника версии не ниже 4.12 и только для языков, поддерживаемых расширением PT for Exam (это языки Pascal и С++, а также, начиная с версии 4.14, язык Python 3), перед вызовом процедуры инициализации группы CreateGroup выполняется проверка перечисленных выше условий. Если хотя бы одно из условий нарушено, то выполняется немедленный выход из процедуры inittaskgroup, и группа ExamDemo не создается.

Следует обратить внимание на наличие символа-метки «^» в начале строки-описания группы. Этот символ отменяет автоматическое преобразование к нижнему регистру первой буквы описания при его выводе в программных модулях PT4Demo и PT4Load (если бы символ «^» отсутствовал, то строка с описанием данной группы имела бы вид «Тема: еГЭ по информатике: примеры различных задач»).

Наконец, в соответствии с правилом 3, мы включили в создаваемую группу преамбулу, в которой отмечается основная особенность данной группы: необходимость использования стандартных средств ввода-вывода (для определения текста преамбулы вызываются процедуры CommentText). Обратите внимание на управляющие последовательности \M\m, позволяющие отобразить фрагмент текста моноширинным шрифтом.

Для успешной компиляции проекта с созданной группой необходимо, чтобы в его каталоге находился файл PT4TaskMaker.pas (этот файл можно скопировать из подкаталога TASKMAKE системного каталога комплекса Teacher Pack for PT4; по умолчанию системный каталог размещается в каталоге Program Files и имеет имя PT4TeacherPack).

Для того чтобы при успешной компиляции можно было сразу просмотреть содержимое созданной группы, достаточно настроить параметры запуска нашего проекта: определить главное приложение (host application), которое будет запускаться при запуске проекта, и параметры главного приложения, которые позволят сразу загрузить в него dll-файл с группой ExamDemo и отобразить задания этой группы на экране (более подробно процесс настройки параметров запуска описан в пункте «Тестирование созданной группы» раздела «Примеры»).

В качестве главного приложения укажем программу PT4Demo, введя в соответствующее поле ее полное имя вместе с путем (например, C:\Program Files\PT4\PT4Demo.exe). В качестве параметров главного приложения укажем следующую строку: -gExamDemo -n999. Благодаря этим параметрам при запуске программы PT4Demo в ней будет выбрана группа ExamDemo, причем сразу отобразится окно задачника с последним заданием данной группы (напомним, что если значение параметра –n превышает число заданий в группе, то параметр полагается равным максимальному допустимому номеру задания):

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

В окне задачника можно просматривать все имеющиеся задания данной группы (нажимая клавиши [Enter] и [Backspace], а также генерировать различные варианты исходных данных и связанных с ними контрольных (т. е. «правильных») результатов. При закрытии окна программа PT4Demo немедленно завершит работу, и мы вернемся в редактор кода среды Delphi. Заметим, что при последующих запусках программы будет автоматически выбираться тот режим окна задачника, в котором оно находилось в момент его предшествующего закрытия.

Если после отображения окна модуля PT4Demo окно задачника не появилось, значит, группа ExamDemo не была добавлена в состав групп задачника (в этом можно убедиться, просмотрев выпадающий список доступных групп в окне модуля PT4Demo). Возможны две причины подобного поведения программы: либо на компьютере установлена старая версия задачника (меньшая 4.12), либо модуль PT4Demo настроен на язык программирования, отличный от языков Pascal, C++ и Python 3. Проверить номер текущей версии задачника, а также при необходимости настроить текущий язык программирования можно с помощью программы настройки PT4Setup, входящей в состав задачника.

Для того чтобы cгенерировать html-страницу с описанием созданной группы (это позволяет, в частности, увидеть текст преамбулы группы), достаточно внести небольшое изменение в параметры командной строки главного приложения, а именно следует дополнить параметр –g символом #, получив в результате строку -gExamDemo# -n999. Теперь при запуске проекта PT4ExamDemo на экране вместо окна задачника с заданием ExamDemo14 появится html-браузер с описанием созданной группы:

Быстрое создание сводной группы

Если новая группа должна содержать только задания, уже входящие в состав существующих групп, то для ее быстрого создания можно использовать конструктор вариантов PTVarMaker, входящий в комплекс Teacher Pack for PT4 (см. команду конструктора вариантов «Действия | Создать сводную группу заданий»).

Файл с определением сводной группы в нашем случае должен содержать следующий текст:

=ExamDemo
=^ЕГЭ по информатике: примеры различных задач
=qdfedsag33gbg45j

-
\PПри выполнении заданий данной группы вместо
специальных операций ввода-вывода, предоставляемых
задачником, необходимо применять стандартные операции
используемого языка программирования: процедуры
\MRead\m/\MReadln\m\:\MWrite\m/\MWriteln\m для языка
Pascal, потоки \Mcin\m\:\Mcout\m для языка C++.
-

ExamBegin 71-72
ExamTaskC 25-36

Обработав данный текст, мы получим файл PT4ExamDemo.dll, содержащий сводную группу заданий, а также файл PT4ExamDemo.html с ее описанием. В конструкторе вариантов предусмотрены средства, позволяющие немедленно просмотреть созданную группу заданий в окне задачника и вывести на экран ее html-описание.

Следует заметить, что подобный способ создания сводных групп для заданий, связанных с ЕГЭ, имеет один недостаток: в нем не выполняется проверки текущей версии задачника и языка программирования. Группа будет создаваться и для языков программирования, отличных от Pascal и C++, однако при попытке ее использования для этих языков будет выводиться сообщение об ошибке «Задание не реализовано для текущего языка программирования».

Добавление новых заданий

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

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

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

function RandR(a, b: integer): real;
begin
  result := RandomN(a*100, b*100)/100;
end;

При реализации функции RandR мы использовали функцию RandomN, входящую в состав конструктора учебных заданий, начиная с версии 4.11 (функция RandomN(M, N) возвращает случайное целое число, лежащее в диапазоне M..N, включая границы диапазона). Функция RandR(a, b) (a и b — целые) возвращает вещественное число, лежащее в диапазоне a..b и имеющее не более двух дробных знаков. Такие числа можно без потери точности записывать в текстовый файл в формате с двумя дробными знаками; таким образом, программа учащегося прочтет из файла именно то число, которое было сгенерировано при инициализации задания. Напомним, что в версии 4.11 конструктора имеется функция RandomR, также предназначенная для генерации случайных вещественных чисел, однако она не позволяет фиксировать число дробных знаков, и поэтому менее пригодна для генерации данных, предназначенных для записи в текстовые файлы.

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

procedure SwapR(var a, b: real);
var
  c: real;
begin
  c := a;
  a := b;
  b := c;
end;

Следующая группа вспомогательных переменных и процедур связана с особенностью заданий типа Exam, описанной в правиле 5 (см. первый пункт данного раздела). Ввиду важности этого правила приведем его еще раз:

  • Набор исходных и контрольных данных надо сохранять в текстовых файлах, передавая задачнику информацию об именах этих файлов (процедурами DataS) и связывая содержимое этих файлов с разделами исходных и результирующих данных (процедурами DataFileT и ResultFileT соответственно).

Таким образом, при инициализации каждого задания группы Exam надо выполнить следующие действия:

  1. Сгенерировать имена файлов, содержащих исходные и контрольные данные (эти имена должны быть различными и меняться при каждом тестовом испытании программы; кроме того, подобно всем файлам, используемым в заданиях, они должны иметь расширение .tst).
  2. Связать созданные имена с файловыми переменными и открыть эти файлы на запись.
  3. Заполнить файлы необходимыми данными.
  4. Закрыть файлы с исходными и контрольными данными.
  5. Передать задачнику информацию об именах созданных файлов, чтобы при выполнении задания эта информация была использована при связывании файлов со стандартными потоками ввода-вывода.
  6. Передать задачнику информацию о том, что первый из созданных файлов должен быть включен в раздел исходных данных, а второй — в раздел результатов; это, во-первых, позволит отобразить содержимое файлов в окне задачника и, во-вторых, обеспечит проверку правильности результирующего файла, созданного программой учащегося (путем его сравнения с данными контрольного файла).

От условий конкретного задания будет зависеть только действие 3, связанное с заполнением файлов нужными данными. Все остальные действия являются стандартными и должны выполняться при инициализации любого задания групп Exam. Поэтому удобно оформить эти действия в виде двух вспомогательных процедур, одна из которых (StartExam) выполняет начальные действия 1–2, а другая (EndExam) — завершающие действия 4–6. Поскольку в каждой из этих процедур необходимо использовать имена созданных файлов и связанные с ними файловые переменные, эти переменные удобно описать как глобальные:

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); stdcall;
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).

Для просмотра новых заданий в окне задачника надо восстановить список параметров запуска, удалив в нем символ # после текста ExamDemo: -gExamDemo -n999

При нажатии клавиши [F9] мы увидим на экране окно задачника в демо-режиме, в котором можно выбрать и просмотреть все задания, включенные к настоящему моменту в нашу группу. Приведем вид окна для задания ExamDemo4 (напомним, что это задание инициализируется посредством вызова процедуры Exam1 с параметром, равным 2):

Добавление заданий повышенной сложности

Наша группа ExamDemo к настоящему моменту содержит 12 заданий повышенной сложности, импортированных из группы ExamTaskC. Все эти задания связаны с общей предметной областью; они содержат сведения об абитуриентах и включают их фамилии, номера школ и годы поступления в вузы. Для того чтобы проиллюстрировать некоторые особенности, связанные с разработкой подобных заданий, дополним набор уже имеющихся заданий двумя новыми заданиями из той же предметной области.

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

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

Добавим к нашей библиотеке вспомогательный массив фамилий из 40 элементов:

const
  famcount = 40;
var
  fam: array[1..famcount] of string =
  ('Иванов', 'Петров', 'Сидоров', 'Кузнецов', 'Филиппов',
   'Сергеев', 'Александров', 'Петухов', 'Пономарев', 'Яшин',
   'Греков', 'Иванова', 'Кузнецова', 'Алексеева', 'Зайцев',
   'Волкова', 'Фролов', 'Юрьев', 'Бондарев', 'Семенов',
   'Семенова', 'Федченко', 'Марченко', 'Борисова', 'Петровский',
   'Беляева', 'Белкин', 'Лысенко', 'Сорокина', 'Пастухов',
   'Юрьева', 'Кондратьев', 'Тимофеев', 'Степанова', 'Якимов',
   'Юсов', 'Степанов', 'Руденко', 'Демидов', 'Леонидов');

Оба новых задания, как и два предыдущих, мы реализуем в виде одной процедуры с параметром m, принимающим значения 1 или 2:

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;

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

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

В процедуре используются два массива: массив a предназначен для хранения контрольных (правильных) результатов, а массив nums является вспомогательным (его назначение описывается далее). Для хранения исходных данных массив не предусматривается, поскольку после генерации полей очередной записи они будут немедленно записываться в исходный файл и обрабатываться.

Количество записей в исходном наборе данных записывается в переменную n. Наборы, содержащие небольшое число записей, удобны при отладке программы (благодаря своей «обозримости»), в то время как большие наборы позволяют проверить программу в «реальной» ситуации и тем самым окончательно убедиться в правильности алгоритма. Используя функцию CurrentTest, добавленную в версию 4.11 конструктора PT4TaskMaker, мы обеспечили дополнительную «настройку» процесса генерации исходных данных: при первом тестовом запуске программы с решением задачи ей всегда предлагается набор из небольшого количества записей (что упрощает поиск и исправление ошибок), а при втором тестовом запуске — большой набор записей (что позволяет проверить предложенный алгоритм «на прочность»). При последующих тестовых запусках (а также при демонстрационном и ознакомительном запуске программы) значение n с равной вероятностью выбирается либо из диапазона 10..20, либо из диапазона 50..100.

В случае генерации исходных данных для указанных заданий при небольших значениях n (10–20) возникает дополнительная проблема: если выбирать случайным образом номера школ из всего допустимого диапазона 1–100, то с большой вероятностью каждый номер школы появится в наборе исходных данных всего по одному разу, что не позволит проверить правильность реализованного в программе алгоритма нахождения минимального/максимального значения. Чтобы решить эту проблему, используется вспомогательный массив nums из 10 элементов, в который заносятся 10 случайно выбранных номеров школ, после чего номера школ для исходного набора записей выбираются уже из этого набора номеров.

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

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

Во втором задании результаты должны упорядочиваться по набору ключей: первый (главный) ключ — максимальный год (сортируется по убыванию), второй (подчиненный) ключ — номер школы (сортируется по возрастанию). Использованный нами способ упорядочивания обеспечивает автоматическую сортировку по подчиненному ключу, так как при поиске очередного максимума массив a перебирается по возрастанию индексов, и поэтому в результате находится номер первого максимального элемента.

Прочие фрагменты процедуры Exam2 дополнительных комментариев не требуют.

Вызов процедуры Exam2 надо добавить в конец оператора case процедуры InitTask, связав его с номерами 17 и 18:

procedure InitTask(num: integer); stdcall;
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;

Кроме того, необходимо опять откорректировать параметр процедуры CreateGroup, определяющий количество заданий, положив его равным 18.

При нажатии клавиши [F9] на экране появится окно задачника с последним заданием данной группы (на рисунке приведен вид окна после прокрутки раздела с формулировкой задания):

Если теперь опять добавить символ # в список параметров запуска (-gExamDemo# -n999), то при нажатии [F9] мы увидим html-страницу с описанием группы, которое теперь содержит не только импортированные, но и реализованные нами задания:

Поскольку при создании новых заданий мы указали в качестве параметра процедур CreateTask названия подгрупп (и разместили новые задания после заданий из данных подгрупп), новые задания отображаются в составе этих подгрупп: ExamDemo3 и ExamDemo4 — в подгруппе «Преобразование массивов», а ExamDemo17 и ExamDemo18 — в подгруппе «Обработка сложных наборов данных».


PrevNext

 

Рейтинг@Mail.ru

Разработка сайта:
М. Э. Абрамян, В. Н. Брагилевский

Последнее обновление:
01.01.2024