Programming Taskbook


E-mail:

Пароль:

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

 

ЮФУ SMBU

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

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

 

Teacher Pack | Конструктор учебных заданий | Примеры

PrevNext


Примеры

В качестве основного языка программирования в примерах используется язык Pascal (среда Delphi, Lazarus или PascalABC.NET). В ряде случаев приводятся также образцы реализации заданий на языках C++ и C#.

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

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

Создадим группу заданий MakerDemo, в которую импортируем два первых задания из базовой группы Begin. Следуя правилам об именовании dll-файлов с группами заданий, дадим нашему проекту имя PT4MakerDemo.

Будем предполагать, что проект создается в среде Borland Delphi. В этом случае файл должен иметь расширение .dpr. В среде Free Pascal Lazarus текст проекта будет таким же; потребуется лишь сохранить его в файле с расширением .lpr и настроить свойства создаваемого проекта, установив режим совместимости с Delphi. Для этого достаточно поместить в начало lpr-файла следующую директиву компилятора:

{$mode delphi}

Кроме того, при разработке проекта и в среде Delphi, и в среде Lazarus целесообразно установить режимы проверки диапазона и поддержки Ansi-строк («длинных строк»). Для этого в начале файла достаточно указать следующую строку:

{$R+,H+}

Режим проверки диапазона полезен, если при разработке заданий используются массивы, поскольку он позволяет сразу выявить ошибки, связанные с выходом индексов элементов массива за допустимый диапазон. Режим длинных строк необходим при использовании в программе функций TextSample и EnTextSample, поскольку они возвращают строки, длина которых превосходит 255 символов.

При подготовке сводной группы в среде PascalABC.NET проект-заготовка будет иметь расширение .pas. Особенности данного проекта подробно описаны в пункте «Структура проекта с описанием группы заданий».

Файл PT4MakerDemo.dpr, содержащий сводную группу заданий, является кратким и имеет стандартную структуру:

[Pascal]

library PT4MakerDemo;

uses PT4TaskMaker;

procedure InitTask(num: integer); stdcall;
begin
  case num of
  1..2: UseTask('Begin', num);
  end;
end;

procedure inittaskgroup;
begin
  CreateGroup('MakerDemo', 'Примеры различных задач',
    'М. Э. Абрамян, 2014', 'qwqfsdf13dfttd', 2, InitTask);
end;

exports inittaskgroup, activate;

begin
end.

К библиотеке подключается модуль PT4TaskMaker, после чего в ней описывается основная процедура группы заданий InitTask, определяющая задание по его номеру. Поскольку мы не создавали своих заданий, в данной процедуре используется только стандартная процедура UseTask, позволяющая импортировать задания из имеющихся групп. В нашем случае импортируются задания с номерами 1 и 2 из группы Begin.

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

Примечание. В приведенном примере список exports содержит также имя процедуры activate. Эта процедура определена в модуле PT4MakerDemo; указывать ее в списке exports необходимо лишь для проектов, разрабатываемых в среде Free Pascal Lazarus (в проектах Delphi данная процедура включается в список exports автоматически). Если при разработке новой группы заданий в среде Lazarus процедура activate не будет включена в список exports, то при попытке вызвать задание из этой группы будет выведено соответствующее сообщение об ошибке.

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

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

Для того чтобы упростить и ускорить тестирование новых групп с применением программы PT4Demo, в версии 4.8 задачника для этой программы были предусмотрены два параметра командной строки: –g<название группы>[#] и –n[–]<номер задания>. Перед описанием действий, связанных с тестированием группы заданий, опишем возможности, предоставляемые данными параметрами.

Если указан параметр –g, содержащий имя существующей группы и оканчивающийся символом #, то при запуске программы PT4Demo сразу создается и отображается на экране html-файл с описанием указанной группы, причем этот файл получает имя PT4<название группы>.html и сохраняется в рабочем каталоге задачника (если указанная группа заданий не найдена, то об этом выводится соответствующее сообщение). После отображения html-файла (или вывода сообщения о том, что группа не найдена) программа PT4Demo немедленно завершается.

Если в конце параметра –g не указан символ #, то при отсутствии указанной группы оба параметра игнорируются, а в случае, если группа существует, действия программы PT4Demo зависят от значения параметра –n. Если параметр –n отсутствует или равен 0, то в окне PT4Demo в списке групп выбирается указанная группа, а номер задания полагается равным 1. Если параметр –n больше нуля, то дополнительно выполняются следующие действия:

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

Если перед номером в параметре –n указан знак «минус», то первое из перечисленных выше дополнительных действий выполняется, а второе — нет (т. е. автоматического завершения работы программы PT4Demo при закрытии окна задачника не происходит).

При использовании мини-варианта задачника список доступных групп имеет дополнительную особенность. Если часть заданий из некоторой группы недоступна для выполнения в мини-варианте, то эта группа представлена в списке дважды: в обычном виде (например, «Array») и в «сокращенном» виде, который помечается символом «°» (например, «Array°») и содержит только те задания, которые доступны для выполнения в мини-варианте. Если же все задания группы доступны для выполнения в мини-варианте, то такая группа указывается в списке один раз и снабжается символом «°» (примером такой группы является группа «Integer°»). Если при запуске программы PT4Demo в мини-варианте требуется отобразить группу, помеченную символом «°», то после ее имени в параметре –g надо указать символ * (звездочка), например, –gArray*. Если же список содержит только имя группы, снабженное символом «°», то символ * в параметре –g можно не указывать: и при его наличии, и при его отсутствии требуемая группа будет найдена. Например, для отображения группы Integer для любого варианта задачника в качестве параметра программы PT4Demo достаточно указать –gInteger.

Вернемся к нашему проекту — сводной группе. Для отображения ее заданий на экране необходимо определить главное приложение (host application), которое будет запускаться при запуске проекта, загружать dll-файл со сводной группой и отображать задания этой группы на экране. В среде Delphi для определения главного приложения следует выполнить команду меню «Run | Parameters...», перейти в появившемся окне на вкладку «Locals» и указать путь к главному приложению в поле «Host Application», а требуемые параметры — в поле «Parameters». Поле «Working Directory» следует оставить пустым; в этом случае рабочим каталогом будет считаться тот каталог, который содержит откомпилированную библиотеку с группой заданий. Аналогичное окно настроек предусмотрено и в среде Lazarus; для его отображения надо выполнить команду меню «Run | Run Parameters...».

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

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

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

Примечание 1. В качестве приложения, тестирующего созданный dll-проект с группой заданий, можно использовать программу, выполняющую одно из заданий этой группы, создав ее с помощью модуля PT4Load, однако перед этим необходимо сделать каталог с dll-проектом рабочим каталогом задачника (используя для этого команду «PT4 Setup» из меню «Programming Taskbook 4», расположенного в группе «Программы» Главного меню Windows) и хотя бы один раз выполнить компиляцию dll-проекта (чтобы в рабочем каталоге появился dll-файл с новой группой заданий). В нашем случае можно создать заготовку для выполнения задания MakerDemo2. Для возможности просмотра всех заданий группы следует вызвать задание MakerDemo2 в демонстрационном режиме, указав после имени задания в процедуре Task символ ?: Task('MakerDemo2?'). При использовании демонстрационного режима можно не указывать номер задания; в этом случае при запуске программы на экране будет отображаться последнее задание данной группы.

Примечание 2. Описанные в данном пункте способы тестирования доступны и при разработке групп заданий в программных средах для языков C++ и C#. Для тестирования группы, разрабатываемой в среде PascalABC.NET, следует использовать программу, выполняющую одно из заданий группы (см. примечание 1). Дополнительные сведения об особенностях отладки библиотек с группами заданий приведены в пункте «Настройка проектов для различных языков и особенности их отладки».

Добавление описания группы и ее подгрупп

По тексту, расположенному выше названия задания MakerDemo2 (см. рисунок), мы видим, что импортированные из группы Begin задания входят в подгруппу с заголовком «Ввод и вывод данных, оператор присваивания». В сводной группе MakerDemo мы можем добавить комментарий (преамбулу) как к самой группе, так и к любой имеющейся в ней подгруппе. Кроме того, мы можем импортировать преамбулу любой имеющейся группы или подгруппы. Для иллюстрации этих возможностей добавим в процедуру inittaskgroup новые операторы (их надо указать после вызова процедуры CreateGroup):

[Pascal]

CommentText('Данная группа демонстрирует различные возможности');
CommentText('\Iконструктора учебных заданий\i \MPT4TaskMaker\m.');

Subgroup('Ввод и вывод данных, оператор присваивания');
CommentText('В этой подгруппе содержатся задания, импортированные');
CommentText('из группы Begin.\PПриводимый ниже абзац преамбулы');
CommentText('также импортирован из данной группы.\P');
UseComment('Begin');

Два первых вызова процедуры CommentText определяют текст преамбулы для группы MakerDemo. Обратите внимание на управляющие последовательности: пара последовательностей \I и \i выделяет курсивный фрагмент, а пара \M и \m выделяет фрагмент, в которым используется моноширинный шрифт. Последующий вызов процедуры Subgroup устанавливает режим определения преамбулы для подгруппы с указанным именем. В тексте этой преамбулы, который, как и текст преамбулы группы, определяется с помощью процедуры CommentText, используется управляющая последовательность \P, обеспечивающая переход к новому абзацу.

Наконец, последняя процедура (UseComment) импортирует преамбулу группы Begin в преамбулу нашей подгруппы «Ввод и вывод данных, оператор присваивания». Имеется также вариант процедуры UseComment, позволяющий импортировать преамбулу подгруппы; в этом варианте следует указать два параметра: имя группы и заголовок требуемой подгруппы, входящей в эту группу. Импортировать преамбулы подгрупп можно только для тех групп заданий, в которых имеется разделение на подгруппы (обычно это группы, содержащие большое количество заданий). В группе Begin деления на подгруппы нет, поэтому из нее можно импортировать только преамбулу самой группы.

Для того чтобы ознакомиться с результатом сделанных изменений, следует сгенерировать html-страницу с текстом группы MakerDemo. Для этого достаточно внести небольшое изменение в параметры командной строки главного приложения, а именно, следует дополнить параметр –g символом #, получив в результате строку -gMakerDemo# -n999. Теперь при запуске проекта PT4MakerDemo на экране вместо окна задачника с заданием MakerDemo2 появится html-браузер с описанием созданной группы:

Обратите внимание на последний абзац в описании подгруппы («Все входные и выходные данные в заданиях этой группы являются вещественными числами»), который был импортирован из группы Begin.

Примечание 1. Вывести html-описание группы можно также, используя программу-заготовку, созданную для выполнения задания MakerDemo2 (см. примечание в предыдущем пункте). Для этого достаточно изменить параметр в процедуре Task, удалив в нем номер задания и добавив символ #: Task('MakerDemo#'). Заметим, что если указать в параметре символ #, не удаляя номер задания (например, Task('MakerDemo2#')), то в html-описание будет включено только задание с указанным номером. При этом будут также выведены комментарии ко всей группе и к той подгруппе, к которой относится выбранное задание. Для включения в html-страницу нескольких заданий (или групп заданий) достаточно для каждого из них вызвать процедуру Task с параметром, оканчивающимся символом #.

Примечание 2. При подготовке заданий на языках C++ и C# необходимо учитывать, что в этих языках символ «\» (обратная косая черта) имеет особый смысл в строковых константах, «экранируя» последующий символ. Чтобы обратная косая черта интерпретировалась как часть управляющей последовательности, необходимо экранировать саму косую черту, указав ее дважды:

[C++]

CommentText("Данная группа демонстрирует различные возможности");
CommentText("\\Iконструктора учебных заданий\\i \\MPT4TaskMaker\\m.");

Subgroup("Ввод и вывод данных, оператор присваивания");
CommentText("В этой подгруппе содержатся задания, импортированные");
CommentText("из группы Begin.\\PПриводимый ниже абзац преамбулы");
CommentText("также импортирован из данной группы.\\P");
UseComment("Begin");

В случае языка C# вместо двойного ввода косой черты можно использовать строки в специальном «дословном» формате:

[C#]

CommentText("Данная группа демонстрирует различные возможности");
CommentText(@"\Iконструктора учебных заданий\i \MPT4TaskMaker\m.");

Subgroup("Ввод и вывод данных, оператор присваивания");
CommentText("В этой подгруппе содержатся задания, импортированные");
CommentText(@"из группы Begin.\PПриводимый ниже абзац преамбулы");
CommentText(@"также импортирован из данной группы.\P");
UseComment("Begin");

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

=MakerDemo
=Примеры различных задач
=qwqfsdf13dfttd

-
Данная группа демонстрирует различные возможности
\Iконструктора учебных заданий\i \MPT4TaskMaker\m.
- Ввод и вывод данных, оператор присваивания
В этой подгруппе содержатся задания,
импортированные из группы Begin.
\PПриводимый ниже абзац преамбулы
также импортирован из данной группы.\P
* Begin
-

Begin 1-2

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

Добавление нового задания

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

[Pascal]

procedure MakerDemo3;
var
  a, b: real;
begin
  CreateTask('Ввод и вывод данных, оператор присваивания');
  TaskText('Даны стороны прямоугольника~{a} и~{b}.', 0, 2);
  TaskText('Найти его площадь {S}~=~{a}\*{b} и периметр {P}~=~2\*({a}\;+\;{b}).',
    0, 4);
  a := RandomN(1, 99) / 10;
  b := RandomN(1, 99) / 10;
  DataR('a = ', a, xLeft, 3, 4);
  DataR('b = ', b, xRight, 3, 4);
  ResultR('S = ', a * b, 0, 2, 4);
  ResultR('P = ', 2 * (a + b), 0, 4, 4);
  SetTestCount(3);
end;

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

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

  • инициализацию нового задания (процедура CreateTask; мы указали в этой процедуре, что данное задание должно входить в подгруппу «Ввод и вывод данных, оператор присваивания», т. е. в ту же подгруппу, что и два предыдущих задания);
  • определение его формулировки (процедуры TaskText; обратите внимание на используемые в этих процедурах управляющие последовательности);
  • определение исходных (процедуры DataR) и результирующих данных (процедуры ResultR); при этом исходные данные генерируются с помощью датчика случайных чисел (процедура RandomN);
  • указание количества успешных тестовых запусков программы учащегося, достаточных для регистрации задания как выполненного (процедура SetTestCount; для нашего простого задания достаточно трех проведенных подряд успешных тестовых запусков).

Необходимо также включить вызов созданной процедуры в основную процедуру группы MakerDemo, связав его с номером 3:

[Pascal]

procedure InitTask(num: integer); stdcall;
begin
  case num of
  1..2: UseTask('Begin', num);
  3: MakerDemo3;
  end;
end;

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

Запустив проект на выполнение, мы увидим в html-описании группы MakerDemo формулировки трех заданий, а удалив из списка параметров главного приложения символ # (в результате список параметров примет вид -gMakerDemo -n999) и повторно запустив проект на выполнение, мы увидим окно задачника с загруженным заданием MakerDemo3. Заметим, что при последующих запусках проекта мы будем получать в окне задачника различные исходные данные; это связано с тем, что в процедуре CreateTask автоматически вызывается процедура Randomize.

Примечание. Реализация задания на языках C++ и C# практически ничем не будет отличаться от реализации на языке Pascal. Это возможно, в частности, благодаря тому, что в набор процедур конструктора для любого языка программирования включена процедура RandomN, позволяющая генерировать случайные целые числа из указанного диапазона. Следует лишь учесть особенность операции деления в этих языках, указав в качестве делителя константу вещественного типа:

[C++]

void MakerDemo3()
{
  CreateTask("Ввод и вывод данных, оператор присваивания");
  TaskText("Даны стороны прямоугольника~{a} и~{b}.", 0, 2);
  TaskText("Найти его площадь {S}~=~{a}\\*{b} и периметр {P}~=~2\\*({a}\\;+\\;{b}).",
    0, 4);
  double a = RandomN(1, 99) / 10.0,
    b = RandomN(1, 99) / 10.0;
  DataR("a = ", a, xLeft, 3, 4);
  DataR("b = ", b, xRight, 3, 4);
  ResultR("S = ", a * b, 0, 2, 4);
  ResultR("P = ", 2 * (a + b), 0, 4, 4);
  SetTestCount(3);
}

[C#]

static void MakerDemo3()
{
    CreateTask("Ввод и вывод данных, оператор присваивания");
    TaskText("Даны стороны прямоугольника~{a} и~{b}.", 0, 2);
    TaskText(@"Найти его площадь {S}~=~{a}\*{b} и периметр {P}~=~2\*({a}\;+\;{b}).",
      0, 4);
    double a = RandomN(1, 99) / 10.0,
        b = RandomN(1, 99) / 10.0;
    DataR("a = ", a, xLeft, 3, 4);
    DataR("b = ", b, xRight, 3, 4);
    ResultR("S = ", a * b, 0, 2, 4);
    ResultR("P = ", 2 * (a + b), 0, 4, 4);
    SetTestCount(3);
}

Добавление заданий на обработку двумерных массивов и символьных строк

Добавим к группе MakerDemo еще два задания: первое из них дублирует задание Matrix7 (подгруппа «Двумерные массивы (матрицы): вывод элементов»), а второе не имеет полного аналога в группе String, однако может быть отнесено к ее первой подгруппе: «Символы и строки: основные операции». Реализуем эти задания в процедурах MakerDemo4 и MakerDemo5:

[Pascal]

procedure MakerDemo4;
var
  m, n, i, j, k: integer;
  a: array [1..5, 1..8] of real;
begin
  CreateTask('Двумерные массивы (матрицы): вывод элементов');
  TaskText('Дана матрица размера~{M}\;\x\;{N} и целое число~{K} (1~\l~{K}~\l~{M}).',
    0, 2);
  TaskText('Вывести элементы {K}-й строки данной матрицы.', 0, 4);
  m := RandomN(2, 5);
  n := RandomN(4, 8);
  k := 1;
  if m = 5 then k := 0;
  DataN('M = ', m, 3, 1, 1);
  DataN('N = ', n, 10, 1, 1);
  for i := 1 to m do
    for j := 1 to n do
    begin
      a[i, j] := RandomR(-9.99, 9.99);
      DataR(a[i,j], Center(j, n, 5, 1), i + k, 5);
    end;
  k := RandomN(1, m);
  DataN('K = ', k, 68, 5, 1);
  for j := 1 to n do
    ResultR(a[k, j], Center(j, n, 5, 1), 3, 5);
  SetTestCount(5);
end;

procedure MakerDemo5;
var
  s: string;
begin
  CreateTask('Символы и строки: основные операции');
  TaskText('Дана непустая строка~{S}.', 0, 2);
  TaskText('Вывести ее первый и последний символ.', 0, 4);
  s := WordSample(RandomN(0, WordCount-1));
  if CurrentTest = 3 then
    while s[1] = s[Length(s)] do
      s := WordSample(RandomN(0, WordCount-1));
  DataS('S = ', s, 0, 3);
  ResultC('Первый символ: ', s[1], xLeft, 3);
  ResultC('Последний символ: ', s[Length(s)], xRight, 3);
  SetTestCount(4);
end;

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

При выводе элементов исходной матрицы и результирующей матричной строки дополнительные комментарии указывать не требуется, поэтому используется вариант процедур DataR и ResultR, в котором комментарий отсутствует (этот вариант процедур групп Data и Result добавлен в версию 4.11 конструктора учебных заданий).

В процедуре MakerDemo5 для получения исходных символьных строк используются функции WordCount и WordSample. С помощью этих функций можно получать различные варианты русских слов. Заметим, что в конструкторе PT4TaskMaker имеются также функции EnWordCount и EnWordSample, с помощью которых можно получать варианты английских слов.

В процедуре MakerDemo5 использована еще одна возможность, появившаяся в версии 4.11 конструктора: функция CurrentTest, возвращающая порядковый номер текущего тестового запуска. Использование этой функции позволяет связать какой-либо особый вариант теста с некоторым номером тестового испытания, и тем самым гарантировать, что программа с решением задачи обязательно будет проверена на этом особом варианте теста. В нашем случае строка S выбирается из набора слов-образцов, среди которых имеется сравнительно большое число слов, начинающихся и оканчивающихся одной и той же буквой. Для более надежного тестирования решения желательно гарантировать, что в наборе тестов будет хотя бы один тест, в котором начальный и конечный символ исходной строки различаются. Разумеется, можно было бы всегда выбирать подобные строки, используя соответствующий цикл while. Однако при наличии функции CurrentTest в этом нет необходимости: достаточно выполнять подобный цикл для единственного теста, например, с номером 3, как это сделано в приведенной реализации задания. В дальнейшем мы рассмотрим более содержательный пример использования функции CurrentTest.

Осталось изменить количество заданий в вызове процедуры CreateGroup на 5 и включить вызовы новых процедур в основную процедуру группы InitTask:

[Pascal]

procedure InitTask(num: integer); stdcall;
begin
  case num of
  1..2: UseTask('Begin', num);
  3: MakerDemo3;
  4: MakerDemo4;
  5: MakerDemo5;
  end;
end;

Приведем вид окна задачника для новых заданий:

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

Добавим к группе MakerDemo еще два задания: первое из них дублирует задание File63 (подгруппа «Символьные и строковые файлы»), а второе — задание Text16 (подгруппа «Текстовые файлы: основные операции»). Реализуем эти задания в процедурах MakerDemo6 и MakerDemo7:

[Pascal]

function FileName(Len: integer): string;
const
  c = '0123456789abcdefghijklmnopqrstuvwxyz';
var
  i: integer;
begin
  result := '';
  for i := 1 to Len do
    result := result + c[RandomN(1, Length(c))];
end;

procedure MakerDemo6;
var
  k, i, j, jmax: integer;
  s1, s2, s3: string;
  fs1: file of ShortString;
  fs2: file of ShortString;
  fc3: file of char;
  s: ShortString;
  c: char;
begin
  CreateTask('Символьные и строковые файлы');
  TaskText(
  'Дано целое число~{K} (>\,0) и строковый файл.'#13 +
  'Создать два новых файла: строковый, содержащий первые {K}~символов'#13 +
  'каждой строки исходного файла, и символьный, содержащий {K}-й символ'#13 +
  'каждой строки (если длина строки меньше~{K}, то в строковый файл'#13 +
  'записывается вся строка, а в символьный файл записывается пробел).'
  );
  s1 := '1' + FileName(5) + '.tst';
  s2 := '2' + FileName(5) + '.tst';
  s3 := '3' + FileName(5) + '.tst';
  Assign(fs1, s1);
  Rewrite(fs1);
  Assign(fs2, s2);
  Rewrite(fs2);
  Assign(fc3, s3);
  Rewrite(fc3);
  k := RandomN(2, 11);
  jmax := 0;
  for i := 1 to RandomN(10, 20) do
  begin
    j := RandomN(2, 16);
    if jmax < j then
      jmax := j;
    s := FileName(j);
    write(fs1, s);
    if j >= k then
      c := s[k]
    else
      c := ' ';
    write(fc3, c);
    s := copy(s, 1, k);
    write(fs2,s);
  end;
  Close(fs1);
  Close(fs2);
  Close(fc3);
  DataN('K = ', k, 0, 1, 1);
  DataS('Имя исходного файла: ', s1, 3, 2);
  DataS('Имя результирующего строкового файла:  ', s2, 3, 4);
  DataS('Имя результирующего символьного файла: ', s3, 3, 5);
  DataComment('Содержимое исходного файла:', xRight, 2);
  DataFileS(s1, 3, jmax + 3);
  ResultComment('Содержимое результирующего строкового файла:',
    0, 2);
  ResultComment('Содержимое результирующего символьного файла:',
    0, 4);
  ResultFileS(s2, 3, k + 3);
  ResultFileC(s3, 5, 4);
end;

procedure MakerDemo7;
var
  p: integer;
  s, s1, s2, s0: string;
  t1, t2: text;
begin
  CreateTask('Текстовые файлы: основные операции');
  TaskText('Дан текстовый файл.', 0, 2);
  TaskText('Удалить из него все пустые строки.', 0, 4);
  s1 := FileName(6) + '.tst';
  s2 := '#' + FileName(6) + '.tst';
  s := TextSample(RandomN(0, TextCount-1));
  Assign(t2, s2);
  Rewrite(t2);
  Assign(t1, s1);
  Rewrite(t1);
  writeln(t2, s);
  Close(t2);
  s0 := #13#10#13#10;
  p := Pos(s0, s);
  while p <> 0 do
  begin
    Delete(s, p, 2);
    p := Pos(s0, s);
  end;
  writeln(t1, s);
  Close(t1);
  ResultFileT(s1, 1, 5);
  Rename(t2, s1);
  DataFileT(s1, 2, 5);
  DataS('Имя файла: ', s1, 0, 1);
  SetTestCount(3);
end;

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

Имена файлов, полученные с помощью функции FileName, дополняются расширением .tst (заметим, что в базовых группах File, Text и Param это расширение используется в именах всех исходных и результирующих файлов).

Функция FileName используется также для генерации элементов строкового файла в процедуре MakerDemo6.

Для того чтобы предотвратить возможность случайного совпадения имен файлов, в процедуре MakerDemo6 к созданным именам добавляются префиксы: 1 для первого файла, 2 для второго, 3 для третьего. В процедуре MakerDemo7 имя временного файла дополняется префиксом #, что также гарантирует его отличие от имени основного файла задания.

В процедуре MakerDemo6 использован новый вариант процедуры TaskText, появившийся в версии 4.11 задачника. В этом варианте процедура TaskText принимает один строковый параметр, который определяет всю формулировку задания, причем в качестве разделителей строк, входящих в формулировку, можно использовать символы #13, #10 или их комбинацию #13#10 (в указанном порядке). Новый вариант процедуры TaskText позволяет более наглядно отобразить формулировку задания и не требует указания дополнительных параметров.

При реализации задания на обработку текстовых файлов для генерации содержимого файла используются функции TextCount и TextSample. Строка, возвращаемая функцией TextSample, представляет собой текст, содержащий маркеры конца строки — символы #13#10. Указанные символы разделяют соседние строки текста (в конце текста маркер конца строки не указывается). Благодаря наличию маркеров конца строки полученный текст можно записать в текстовый файл с помощью единственной процедуры writeln, которая, кроме записи текста, обеспечивает добавление маркера конца строки в конец файла.

После разработки новых заданий необходимо изменить количество заданий в вызове процедуры CreateGroup на 7 и включить вызовы новых процедур в основную процедуру группы InitTask:

[Pascal]

procedure InitTask(num: integer); stdcall;
begin
  case num of
  1..2: UseTask('Begin', num);
  3: MakerDemo3;
  4: MakerDemo4;
  5: MakerDemo5;
  6: MakerDemo6;
  7: MakerDemo7;
  end;
end;

Приведем вид окна задачника для новых заданий:

При использовании языков C++ и C# для реализации заданий, связанных с файлами, необходимо применять стандартные средства этих языков, предназначенные для обработки файлов. Кроме того, надо учитывать дополнительные условия при работе с файлами, содержащими текстовую информацию. Во-первых, все подобные файлы (двоичные символьные, двоичные строковые, текстовые) должны содержать информацию в однобайтной ANSI-кодировке (в то время как по умолчанию для языка C# применяется многобайтная кодировка UTF-8). Во-вторых, двоичные строковые файлы при реализации задания на любом языке должны иметь формат типизированного файла языка Pascal: file of ShortString. Это означает, что каждый строковый элемент такого файла должен иметь размер 256 байт, причем в начальном байте должен содержаться фактический размер элемента, затем должны следовать символы данной строки, а завершающая часть 256-байтного блока может содержать произвольные символы (например, пробелы). Получая файловые данные, подготовленные в формате языка Pascal на этапе инициализации задания, задачник автоматически преобразует эти данные к формату, принятому для того языка, на котором задание выполняется.

Ниже приводятся варианты реализации заданий MakerDemo6 и MakerDemo7 для языков C++ и C#. Для успешной компиляции данных фрагментов в программе на языке C++ надо указать дополнительные директивы #include <string> и #include <fstream>, а в программе на языке C# — директивы using System.Text; и using System.IO;. В каждой из приведенных реализаций используется вспомогательная функция, предназначенная для преобразования строковых данных в формат, используемый в строковых файлах языка Pascal. Для языка C++ это функция ConvertToPascalStr(string s, char buf[256]), формирующая на основе строки s символьный буфер buf, содержимое которого соответствует формату типа ShortString; для языка C# это функция WritePascalString(BinaryWriter bw, string s), обеспечивающая запись строки s в файл, связанный с потоком bw, в формате, соответствующем формату файла типа file of ShortString.

[C++]

string FileName(int Len)
{
  string result = "";
  int L = c.length();
  for (int i = 0; i < Len; i++)
    result = result + c[RandomN(0, L - 1)];
  return result;
}

void ConvertToPascalStr(string s, char buf[256])
{
  buf[0] = (char)s.length();
  for (size_t i = 1; i <= s.length(); i++)
    buf[i] = s[i-1];
}

void MakerDemo6()
{
  CreateTask("Символьные и строковые файлы");
  TaskText("Дано целое число~{K} (>\\,0) и строковый файл.\n"
  "Создать два новых файла: строковый, содержащий первые {K}~символов\n"
  "каждой строки исходного файла, и символьный, содержащий {K}-й символ\n"
  "каждой строки (если длина строки меньше~{K}, то в строковый файл\n"
  "записывается вся строка, а в символьный файл записывается пробел).");
  string s1 = "1" + FileName(5) + ".tst",
    s2 = "2" + FileName(5) + ".tst",
    s3 = "3" + FileName(5) + ".tst";
  fstream fs1(s1.c_str(), ios_base::binary | ios_base::out),
    fs2(s2.c_str(), ios::binary | ios_base::out),
    fc3(s3.c_str(), ios::binary | ios_base::out);
  int k = RandomN(2, 11), jmax = 0, n = RandomN(10, 20);
  for (int i = 0; i < n; i++)
  {
    char buf[256];
    int j = RandomN(2, 16);
    if (jmax < j)
      jmax = j;
    string s = FileName(j);
    ConvertToPascalStr(s, buf);
    fs1.write(buf, 256);
    char c = ' ';
    if (j >= k)
    {
      c = s[k-1];
      buf[0] = (char)k;
    }
    fs2.write(buf, 256);
    fc3.write((char*) &c, 1);
  }
  fs1.close();
  fs2.close();
  fc3.close();
  DataN("K = ", k, 0, 1, 1);
  DataS("Имя исходного файла: ", s1.c_str(), 3, 2);
  DataS("Имя результирующего строкового файла:  ", s2.c_str(), 3, 4);
  DataS("Имя результирующего символьного файла: ", s3.c_str(), 3, 5);
  DataComment("Содержимое исходного файла:", xRight, 2);
  DataFileS(s1.c_str(), 3, jmax + 3);
  ResultComment("Содержимое результирующего строкового файла:", 0, 2);
  ResultComment("Содержимое результирующего символьного файла:", 0, 4);
  ResultFileS(s2.c_str(), 3, k + 3);
  ResultFileC(s3.c_str(), 5, 4);
}

void MakerDemo7()
{
  CreateTask("Текстовые файлы: основные операции");
  TaskText("Дан текстовый файл.", 0, 2);
  TaskText("Удалить из него все пустые строки.", 0, 4);
  string s1 = FileName(6) + ".tst",
    s2 = "#" + FileName(6) + ".tst",
    s = TextSample(RandomN(0, TextCount() - 1));
  ofstream t2(s2.c_str()), t1(s1.c_str());
  t2 << s << endl;
  t2.close();
  char* s0 = "\r\n\r\n";
  size_t p = s.find(s0);
  while (p != string::npos)
  {
    s.erase(p, 2);
    p = s.find(s0);
  }
  t1 << s << endl;
  t1.close();
  ResultFileT(s1.c_str(), 1, 5);
  rename(s2.c_str(), s1.c_str());
  DataFileT(s1.c_str(), 2, 5);
  DataS("Имя файла: ", s1.c_str(), 0, 1);
  SetTestCount(3);
}

[C#]

const string c = "0123456789abcdefghijklmnopqrstuvwxyz";

static string FileName(int Len)
{
    StringBuilder result = new StringBuilder(Len);
    for (int i = 0; i < Len; ++i)
        result.Append(c[RandomN(0, c.Length - 1)]);
    return result.ToString();
}

static void WritePascalString(BinaryWriter bw, string s)
{
    bw.Write((char)s.Length);
    foreach (char c in s)
        bw.Write(c);
    for (int i = s.Length + 2; i <= 256; i++)
        bw.Write(' ');
}

static void MakerDemo6()
{
    CreateTask("Символьные и строковые файлы");
    TaskText(
    @"Дано целое число~{K} (>\,0) и строковый файл.
    Создать два новых файла: строковый, содержащий первые {K}~символов
    каждой строки исходного файла, и символьный, содержащий {K}-й символ
    каждой строки (если длина строки меньше~{K}, то в строковый файл
    записывается вся строка, а в символьный файл записывается пробел)."
    );
    string s1 = "1" + FileName(5) + ".tst",
        s2 = "2" + FileName(5) + ".tst",
        s3 = "3" + FileName(5) + ".tst";
    BinaryWriter fs1 = new BinaryWriter(File.Create(s1), Encoding.Default),
        fs2 = new BinaryWriter(File.Create(s2), Encoding.Default),
        fc3 = new BinaryWriter(File.Create(s3), Encoding.Default);
    int k = RandomN(2, 11), jmax = 0, n = RandomN(10, 20);
    for (int i = 0; i < n; i++)
    {
        int j = RandomN(2, 16);
        if (jmax < j)
            jmax = j;
        string s = FileName(j);
        WritePascalString(fs1, s);
        char c = ' ';
        if (j >= k)
        {
            c = s[k - 1];
            s = s.Substring(0, k);
        }
        WritePascalString(fs2, s);
        fc3.Write(c);
    }
    fs1.Close();
    fs2.Close();
    fc3.Close();
    DataN("K = ", k, 0, 1, 1);
    DataS("Имя исходного файла: ", s1, 3, 2);
    DataS("Имя результирующего строкового файла:  ", s2, 3, 4);
    DataS("Имя результирующего символьного файла: ", s3, 3, 5);
    DataComment("Содержимое исходного файла:", xRight, 2);
    DataFileS(s1, 3, jmax + 3);
    ResultComment("Содержимое результирующего строкового файла:", 0, 2);
    ResultComment("Содержимое результирующего символьного файла:", 0, 4);
    ResultFileS(s2, 3, k + 3);
    ResultFileC(s3, 5, 4);
}

static void MakerDemo7()
{
    CreateTask("Текстовые файлы: основные операции");
    TaskText("Дан текстовый файл.", 0, 2);
    TaskText("Удалить из него все пустые строки.", 0, 4);
    string s1 = FileName(6) + ".tst",
        s2 = "#" + FileName(6) + ".tst",
        s = TextSample(RandomN(0, TextCount() - 1));
    StreamWriter t1 = new StreamWriter(s1, false, Encoding.Default),
        t2 = new StreamWriter(s2, false, Encoding.Default);
    t2.WriteLine(s);
    t2.Close();
    string s0 = "\r\n\r\n";
    int p = s.IndexOf(s0);
    while (p > 0)
    {
        s = s.Remove(p, 2);
        p = s.IndexOf(s0);
    }
    t1.WriteLine(s);
    t1.Close();
    ResultFileT(s1, 1, 5);
    File.Move(s2, s1);
    DataFileT(s1, 2, 5);
    DataS("Имя файла: ", s1, 0, 1);
    SetTestCount(3);
}

Следует обратить внимание на особенности использования для языков C++ и C# нового варианта функции TaskText (см. задание MakerDemo6). В случае языка C++ достаточно завершать каждый строковый литерал (соответствующий одной строке формулировки задания) символом \n (или \r), располагая строковые литералы один под другим без каких-либо объединяющих операций. В случая языка C# используется дословная строка, в которой допускается выполнять переход на новую экранную строку; при этом в месте перехода строка будет содержать соответствующий символ разрыва строки.

Добавление заданий на обработку динамических структур данных

Наконец, добавим в нашу группу задание, посвященное обработке динамических структур данных, причем представим его в двух вариантах: традиционном, основанном на использовании записей типа TNode и связанных с ними указателей типа PNode, и «объектном», характерном для .NET-языков (C#, Visual Basic .NET, PascalABC.NET), а также языков Python, Java и Ruby. Следует подчеркнуть, что при разработке как традиционного, так и объектного варианта заданий на динамические структуры надо использовать типы TNode и PNode и связанные с ними процедуры конструктора учебных заданий. В то же время, при выполнении объектного варианта задания на соответствующем языке требуется использовать объекты типа Node (которые при разработке задания не применяются).

Задание, которое мы реализуем, дублирует задание Dynamic30, посвященное преобразованию односвязного списка в двусвязный (подгруппа «Динамические структуры данных: двусвязный список»). Оформим два варианта этого задания в виде процедур MakerDemo8 и MakerDemo8Obj:

[Pascal]

var WrongNode: TNode;

procedure MakerDemo8Data;
var
  i, n: integer;
  p, p1, p2: PNode;
begin
  if RandomN(1, 4) = 1 then
    n := 1
  else
    n := RandomN(2, 9);
  case CurrentTest of
  2: n := 1;
  4: n := RandomN(2, 9);
  end;
  new(p1);
  p1^.Data := RandomN(10, 99);
  p1^.Prev := nil;
  p2 := p1;
  for i := 2 to n do
  begin
    new(p);
    p^.Data := RandomN(10, 99);
    p^.Prev := p2;
    p2^.Next := p;
    p2 := p;
  end;
  p2^.Next := nil;
  SetPointer(1, p1);
  SetPointer(2, p2);
  ResultP('Последний элемент: ', 2, 0, 2);
  ResultList(1, 0, 3);
  ShowPointer(2);
  DataP(1, 0, 2);
  p := p1;
  for i := 1 to n do
  begin
    p^.Prev := @WrongNode;
    p := p^.Next;
  end;
  DataList(1, 0, 3);
  ShowPointer(1);
end;

procedure MakerDemo8;
begin
  CreateTask('Динамические структуры данных: двусвязный список');
  TaskText('Дан указатель~{P}_1 на начало непустой цепочки ' +
    'элементов-записей типа TNode,', 0, 1);
  TaskText('связанных между собой с помощью поля Next. Используя ' +
    'поле Prev записи TNode,', 0, 2);
  TaskText('преобразовать исходную (\Iодносвязную\i) цепочку ' +
    'в \Iдвусвязную\i, в которой каждый', 0, 3);
  TaskText('элемент связан не только с последующим элементом ' +
    '(с помощью поля Next),', 0, 4);
  TaskText('но и с предыдущим (с помощью поля Prev). Поле Prev ' +
    'первого элемента положить', 0, 5);
  TaskText('равным \N. Вывести указатель на последний элемент ' +
    'преобразованной цепочки.', 0, 0);
  MakerDemo8Data;
end;

procedure MakerDemo8Obj;
begin
  CreateTask('Динамические структуры данных: двусвязный список');
  TaskText(
  'Дана ссылка~{A}_1 на начало непустой цепочки элементов-объектов типа Node,'#13 +
  'связанных между собой с помощью своих свойств Next. Используя свойства Prev'#13 +
  'данных объектов, преобразовать исходную (\Iодносвязную\i) цепочку в \Iдвусвязную\i,'#13 +
  'в которой каждый элемент связан не только с последующим элементом (с помощью'#13 +
  'свойства Next), но и с предыдущим (с помощью свойства Prev). Свойство Prev'#13 +
  'первого элемента положить равным \O. Вывести ссылку~{A}_2 на последний'#13 +
  'элемент преобразованной цепочки.'
  );
  SetObjectStyle;
  MakerDemo8Data;
end;

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

Однако для языка PascalABC.NET требуемую настройку необходимо выполнить явно, так как в нем можно использовать оба варианта представления динамических структур: традиционный (как для обычного Паскаля в системах Delphi и Free Pascal Lazarus) и объектный (как в языках C#, Visual Basic .NET, Python, Java, Ruby). Для того чтобы представление динамических данных при выполнении задания в среде PascalABC.NET соответствовало объектному варианту, следует в начале процедуры, реализующей задание (перед вызовом любых процедур, связанных с указателями и динамическими структурами), вызвать специальную процедуру без параметров SetObjectStyle. Для остальных языков данная процедура не выполняет никаких действий.

Обратите внимание на возможность использования в формулировке задания более 5 экранных строк. Строки, которые не умещаются в области формулировки задания, следует добавлять к заданию процедурой TaskText, указывая в качестве последнего параметра процедуры число 0 (см. процедуру MakerDemo8). Еще проще задавать «длинные» формулировки заданий с помощью нового варианта процедуры TaskText с единственным строковым параметром, содержащим все строки формулировки (см. процедуру MakerDemo9). При наличии подобных строк в окне задачника (если окно находится в режиме с фиксированной компоновкой) слева от области формулировки появятся кнопки, обеспечивающие прокрутку формулировки задания; кроме этих кнопок для прокрутки можно также использовать стандартные клавиши, в частности, клавиши со стрелками.

Для того чтобы имя нулевого указателя (или объекта) соответствовало используемому языку программирования, в формулировке задания применяются управляющие последовательности \N (имя нулевого указателя) и \O (имя нулевого объекта). Для языка PascalABC.NET обе эти последовательности генерируют текст nil.

Достаточно часто алгоритмы, разработанные учащимися для обработки динамических структур данных, дают неверные результаты в случае особых (хотя и допустимых) структур, например, состоящих только из одного элемента. Поэтому желательно предусмотреть появление подобных структур в тестовых наборах исходных данных. В наших заданиях исходный список, состоящий из одного элемента, будет предлагаться программе учащегося в среднем один раз при каждых четырех тестовых испытаниях. Кроме того, благодаря использованию функции CurrentTest, появившейся в версии 4.11 конструктора, вариант списка с единственным элементом будет предложен программе учащегося для обработки в тесте номер 2, а вариант списка с более чем одним элементом — в тесте номер 4. Таким образом, можно гарантировать, что при прохождении набора из 5 тестовых испытаний программе будут предложены как «стандартные», так и «особые» наборы исходных данных.

При формировании односвязной структуры неиспользуемые поля Prev для каждого элемента структуры следует положить равными адресу «фиктивного» элемента (в нашем случае — переменной WrongNode), не связанного с данной структурой. Заметим, что для всех элементов, кроме первого, значения поля Prev можно было бы положить равными nil, однако это не подходит для первого элемента: если поле Prev первого элемента будет равно nil, то слева от него будет выведен «лишний» (в данной ситуации) текст nil<.

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

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

  • имеются языки, для которых отсутствует возможность выполнять задания на обработку динамических структур (например, Visual Basic и 1C);
  • в языках платформы .NET, а также Python, Java и Ruby, необходимо использовать «объектный» стиль формулировок вместо стиля, основанного на указателях и применяемого для языков Pascal и C++.

Кроме того, следует определиться с выбором стиля для языка PascalABC.NET, поскольку в нем можно использовать как стиль указателей, так и стиль объектов. Можно, например, включить в группу заданий для языка PascalABC.NET оба варианта каждого задания.

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

С учетом этих замечаний изменим основную процедуру группы InitTask следующим образом:

[Pascal]

procedure InitTask(num: integer); stdcall;
begin
  case num of
  1..2: UseTask('Begin', num);
  3: MakerDemo3;
  4: MakerDemo4;
  5: MakerDemo5;
  6: MakerDemo6;
  7: MakerDemo7;
  8: if CurrentLanguage and lgWithPointers <> 0 then
       MakerDemo8
     else
       MakerDemo8Obj;
  9: MakerDemo8Obj;
  end;
end;

В этой процедуре используется функция CurrentLanguage, позволяющая определить текущий язык программирования, используемый задачником. Если текущий язык относится к категории языков, поддерживающих указатели (в том числе PascalABC.NET), то в качестве задания номер 8 вызывается процедура MakerDemo8, в которой задание формулируется в терминах указателей. В противном случае вызывается вариант задания, использующий объектную терминологию. При использовании языка PascalABC.NET число заданий в группе будет равно 9; при этом дополнительное задание номер 9 будет представлять собой «объектный» вариант задания номер 8.

Функцию CurrentLanguage потребуется использовать и в начале процедуры inittaskgroup для того, чтобы правильно определить количество заданий в группе для разных языков программирования. Приведем фрагмент, на который надо заменить вызов процедуры CreateGroup и предшествующее ему ключевое слово begin (обратите внимание на то, что теперь в качестве предпоследнего параметра процедуры CreateGroup используется переменная n):

[Pascal]

var
  n: integer;
begin
  n := 7;
  if CurrentLanguage = lgPascalABCNET then
    n := 9
  else
  if CurrentLanguage and (lgWithPointers or lgWithObjects) <> 0 then
    n := 8;
  CreateGroup('MakerDemo', 'Примеры различных задач',
    'М. Э. Абрамян, 2013', 'qwqfsdf13dfttd', n, InitTask);

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

Вариант конструктора для языка C# не поддерживает возможность определения новых заданий, связанных с динамическими структурами данных. Конструктор для языка C++ позволяет разрабатывать подобные задания, причем их программная реализация по существу не отличается от реализации на языке Pascal. В качестве примера приведем реализацию процедуры MakerDemo8Data на языке C++:

[C++]

TNode WrongNode;

void MakerDemo8Data()
{
  int n = 1;
  if (RandomN(1, 4) != 1)
    n = RandomN(2, 9);
  switch (CurrentTest())
  {
  case 2:
    n = 1;
    break;
  case 4:
    n = RandomN(2, 9);
    break;
  }
  PNode p1 = new TNode;
  p1->Data = RandomN(10, 99);
  p1->Prev = NULL;
  PNode p2 = p1, p;
  for (int i = 2; i <= n; i++)
  {
    p = new TNode;
    p->Data = RandomN(10, 99);
    p->Prev = p2;
    p2->Next = p;
    p2 = p;
  }
  p2->Next = NULL;
  SetPointer(1, p1);
  SetPointer(2, p2);
  ResultP("Последний элемент: ", 2, 0, 2);
  ResultList(1, 0, 3);
  ShowPointer(2);
  DataP(1, 0, 2);
  p = p1;
  for (int i = 1; i <= n; i++)
  {
    p->Prev = &WrongNode;
    p = p->Next;
  }
  DataList(1, 0, 3);
  ShowPointer(1);
}

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

[C++]

int n = 7;
if (CurrentLanguage() == lgPascalABCNET)
  n = 9;
else
if ((CurrentLanguage() & (lgWithPointers | lgWithObjects)) != 0)
  n = 8;

Новые задания следует протестировать для различных языков программирования. Для этого можно воспользоваться еще одной новой возможностью программы PT4Demo, появившейся в версии 4.8 задачника: выбором языка программирования с помощью контекстного меню. В данной ситуации следует изменить список параметров главного приложения, положив параметр –n равным 0: -gMakerDemo -n0. Благодаря этому изменению при запуске проекта станет доступно окно программы PT4Demo, в котором можно будет изменить язык программирования (для этого достаточно нажать правую кнопку мыши и выбрать требуемый язык из появившегося контекстного меню). Используя программу PT4Demo, не удастся проверить группу лишь для языка PascalABC.NET. Подобную проверку можно выполнить только в среде PascalABC.NET, выбрав в ней в качестве текущего тот каталог, в котором располагается dll-файл с созданной группой.

Приведем вид окна задачника для новых заданий (первое окно соответствует языку Pascal, второе — языку C#). Обратите внимание на кнопки, расположенные справа от формулировки задания и обеспечивающие ее прокрутку.

Завершая оформление модуля PT4MakerDemo, добавим комментарии к новым подгруппам заданий (указанные операторы следует поместить в конец процедуры inittaskgroup):

  Subgroup('Двумерные массивы (матрицы): вывод элементов');
  CommentText('Данное задание дублирует задание Matrix7.');

  Subgroup('Символьные и строковые файлы');
  CommentText('Данное задание дублирует задание File63.');
  CommentText('Оно демонстрирует особенности, связанные с двоичными');
  CommentText('\Iстроковыми\i файлами.');

  Subgroup('Текстовые файлы: основные операции');
  CommentText('Данное задание дублирует задание Text16.');

  Subgroup('Динамические структуры данных: двусвязный список');
  CommentText('Данное задание дублирует задание Dynamic30.');
  CommentText('\PЗадание реализовано в двух вариантах: основанном');
  CommentText('на использовании указателей');
  CommentText('(для языков Pascal и C++) и основанном на использовании');
  CommentText('объектов (для языков платформы .NET,');
  CommentText('а также Python, Java и Ruby).');
  CommentText('Для языка Visual Basic это задание недоступно.');
  CommentText('В системе PascalABC.NET доступны оба варианта задания.');

Приведем заключительную часть html-страницы с описанием данной группы после установки C# в качестве текущего языка:


PrevNext

 

Рейтинг@Mail.ru

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

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