|
|
Примеры
Создание простейшей сводной группы
Вначале приведем пример создания наиболее простого варианта группы заданий сводной группы,
в которой не разрабатываются новые задания, а лишь производится перекомпоновка заданий из имеющихся групп. Создадим группу
заданий MakerDemo, в которую импортируем два первых задания из базовой группы Begin. Следуя правилам об именовании
dll-файлов с группами заданий, дадим нашему проекту имя PT4MakerDemo.
Будем предполагать, что проект создается в среде Borland Delphi. В этом случае файл должен иметь расширение .dpr.
В среде Free Pascal Lazarus текст проекта будет таким же; потребуется лишь сохранить его в файле с расширением .lpr
и настроить свойства создаваемого проекта, установив режим совместимости с Delphi.
Для этого достаточно поместить в начало lpr-файла следующую директиву компилятора:
{$mode delphi}
Кроме того, при разработке проекта и в среде Delphi, и в среде Lazarus целесообразно установить режимы проверки диапазона и
поддержки Ansi-строк («длинных строк»). Для этого в начале файла достаточно указать следующую строку:
{$R+,H+}
Режим проверки диапазона
полезен, если при разработке заданий используются массивы, поскольку он позволяет сразу выявить ошибки, связанные
с выходом индексов элементов массива за допустимый диапазон.
Режим длинных строк необходим при использовании в программе функций TextSample и EnTextSample,
поскольку они возвращают строки, длина которых превосходит 255 символов.
Файл PT4MakerDemo.dpr, содержащий сводную группу заданий, является кратким и имеет стандартную структуру:
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', 'Примеры различных задач',
'М. Э. Абрамян, 2009', '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 системного каталога комплекса 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 превышает число заданий в группе, то параметр полагается равным максимальному
допустимому номеру задания):
В появившемся окне задачника можно просматривать все имеющиеся задания данной группы, а при его закрытии
программа PT4Demo немедленно завершит работу, и мы вернемся в редактор кода среды Delphi.
Примечание. В качестве приложения, тестирующего созданный dll-проект с группой заданий, можно
использовать программу, выполняющую одно из заданий этой группы, создав ее с помощью модуля PT4Load, однако
перед этим необходимо сделать каталог с dll-проектом рабочим каталогом задачника (используя для этого команду «PT4 Setup»
из меню «Programming Taskbook 4», расположенного в группе «Программы» Главного меню Windows)
и хотя бы один раз выполнить компиляцию dll-проекта (чтобы в рабочем каталоге появился dll-файл с новой группой заданий).
В нашем случае можно создать заготовку для выполнения задания MakerDemo2. Для возможности просмотра всех заданий
группы следует вызвать задание MakerDemo2 в демонстрационном режиме,
указав после имени задания в процедуре Task символ ?: Task('MakerDemo2?').
При использовании демонстрационного режима можно не указывать номер задания; в этом случае при запуске программы на экране будет отображаться
последнее задание данной группы.
Добавление описания группы и ее подгрупп
По тексту, расположенному выше названия задания MakerDemo2 (см. рисунок), мы видим, что импортированные из группы Begin задания
входят в подгруппу с заголовком «Ввод и вывод данных, оператор присваивания».
В сводной группе MakerDemo мы можем добавить комментарий (преамбулу) как к самой группе, так и к любой имеющейся в ней
подгруппе. Кроме того, мы можем импортировать
преамбулу любой имеющейся группы или подгруппы. Для иллюстрации этих возможностей добавим в процедуру inittaskgroup
новые операторы
(их надо указать после вызова процедуры CreateGroup):
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.
Примечание. Вывести html-описание группы можно также, используя программу-заготовку, созданную для выполнения
задания MakerDemo2 (см. примечание в предыдущем пункте). Для этого достаточно изменить параметр в процедуре Task,
удалив в нем номер задания и добавив символ #: Task('MakerDemo#'). Заметим, что
если указать в параметре символ #, не удаляя номер задания (например, Task('MakerDemo2#')), то в html-описание
будет включено только задание с указанным номером. При этом будут также выведены комментарии ко всей группе и к той подгруппе, к
которой относится выбранное задание. Для включения в html-страницу нескольких заданий (или групп заданий) достаточно для каждого из них
вызвать процедуру Task с параметром, оканчивающимся символом #.
Завершая данный пункт, заметим, что сводные группы, т. е. новые наборы уже имеющихся заданий, можно создавать более
простым способои, не разрабатывая проекты в Borland Delphi или Free Pascal Lazarus. Достаточно подготовить текстовый
файл с определением сводной группы и затем обработать его с помощью программы
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
(таким образом, название процедуры будет соответствовать имени создаваемого задания, хотя это и не является обязательным):
procedure MakerDemo3;
var
a, b: real;
begin
CreateTask('Ввод и вывод данных, оператор присваивания');
TaskText('Даны стороны прямоугольника~{a} и~{b}.', 0, 2);
TaskText('Найти его площадь {S}~=~{a}\*{b} и периметр ' +
'{P}~=~2\*({a}\;+\;{b}).', 0, 4);
a := (1 + Random(100)) / 10;
b := (1 + Random(100)) / 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 включает все основные действия, используемые при формировании нового задания:
- инициализацию нового задания (процедура CreateTask;
мы указали в этой процедуре, что данное задание должно входить
в подгруппу «Ввод и вывод данных, оператор присваивания», т. е. в ту же подгруппу, что и два
предыдущих задания);
- определение его формулировки (процедуры TaskText; обратите внимание на используемые в
этих процедурах управляющие последовательности);
- определение исходных (процедуры DataR) и результирующих данных
(процедуры ResultR);
- указание количества успешных тестовых запусков программы учащегося, достаточных для регистрации задания как выполненного
(процедура SetTestCount;
для нашего простого задания достаточно трех проведенных подряд успешных тестовых запусков).
Необходимо также включить вызов созданной процедуры в основную процедуру группы MakerDemo, связав его с номером 3:
procedure InitTask(num: integer);
begin
case num of
1..2: UseTask('Begin', num);
3: MakerDemo3;
end;
end;
Наконец, следует откорректировать число заданий в вызове процедуры CreateGroup, изменив его на 3.
Запустив проект на выполнение, мы увидим в html-описании группы MakerDemo формулировки трех заданий,
а удалив из списка параметров главного приложения символ # (в результате список параметров примет вид
-gMakerDemo -n999) и повторно запустив проект на выполнение, мы увидим окно задачника
с загруженным заданием MakerDemo3. Заметим, что при последующих запусках проекта мы будем получать
в окне задачника различные исходные данные; это связано с тем, что в процедуре CreateTask автоматически вызывается
процедура Randomize.
Добавление заданий на обработку двумерных массивов и символьных строк
Добавим к группе MakerDemo еще два задания: первое из них дублирует задание Matrix7
(подгруппа «Двумерные массивы (матрицы): вывод элементов»), а второе
не имеет полного аналога в группе String, однако может быть отнесено к ее первой
подгруппе: «Символы и строки: основные операции». Реализуем эти задания
в процедурах MakerDemo4 и MakerDemo5:
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 := 2 + Random(4);
n := 4 + Random(5);
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] := 9.98 * Random;
DataR('', a[i,j], Center(j, n, 4, 2), i + k, 4);
end;
k := 1 + Random(m);
dataN('K = ', k, 68, 5, 1);
for j := 1 to n do
ResultR('', a[k, j], Center(j, n, 4, 2), 3, 4);
end;
procedure MakerDemo5;
var
s: string;
begin
CreateTask('Символы и строки: основные операции');
TaskText('Дана строка~{S}.', 0, 2);
TaskText('Вывести ее первый и последний символ.', 0, 4);
s := WordSample(Random(WordCount));
DataS('S = ', s, 0, 3);
ResultC('Первый символ: ', s[1], xLeft, 3);
ResultC('Последний символ: ', s[length(s)], xRight, 3);
SetTestCount(4);
end;
Обратите внимание на использование вспомогательной функции Center
для центрирования строк матрицы в области
исходных и результирующих данных: каждый элемент матрицы занимает 4 экранные позиции, а между
элементами размещается по два пробела. В процедуре MakerDemo4 не вызывается процедура SetTestCount; в этом случае
число успешных тестов, необходимых для регистрации задания как выполненного, по умолчанию полагается равным 5.
В процедуре MakerDemo5 для получения исходных символьных строк используются
функции WordCount и WordSample. С помощью этих функций можно получать
различные варианты русских слов. Заметим, что в конструкторе PT4TaskMaker имеются также функции
EnWordCount и EnWordSample, с помощью которых можно получать варианты английских слов.
Осталось изменить количество заданий в вызове процедуры CreateGroup на 5 и
включить вызовы новых процедур в основную процедуру группы InitTask:
procedure InitTask(num: integer);
begin
case num of
1..2: UseTask('Begin', num);
3: MakerDemo3;
4: MakerDemo4;
5: MakerDemo5;
end;
end;
Приведем вид окна задачника для новых заданий:
Добавление заданий на обработку файлов
Добавим к группе MakerDemo еще два задания: первое из них дублирует задание File63
(подгруппа «Символьные и строковые файлы»), а второе
задание Text16 (подгруппа «Текстовые файлы: основные операции»). Реализуем эти задания
в процедурах MakerDemo6 и MakerDemo7:
function FileName(Len: integer): string;
const
c = '0123456789abcdefghijklmnopqrstuvwxyz';
var
i: integer;
begin
result := '';
for i := 1 to Len do
result := result + c[Random(Length(c))+1];
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) и строковый файл.', 0, 1);
TaskText('Создать два новых файла: строковый, содержащий ' +
'первые {K}~символов', 0, 2);
TaskText('каждой строки исходного файла, и символьный, ' +
'содержащий {K}-й символ', 0, 3);
TaskText('каждой строки (если длина строки меньше~{K}, ' +
'то в строковый файл', 0, 4);
TaskText('записывается вся строка, а в символьный файл ' +
'записывается пробел).', 0, 5);
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 := 2 + Random(10);
jmax := 0;
for i := 1 to 10 + Random(20) do
begin
j := 2 + Random(15);
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, 2);
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: string;
t1, t2: text;
begin
CreateTask('Текстовые файлы: основные операции');
TaskText('Дан текстовый файл.', 0, 2);
TaskText('Удалить из него все пустые строки.', 0, 4);
s1 := FileName(6) + '.tst';
s2 := '#' + FileName(6) + '.tst';
s := TextSample(Random(TextCount));
Assign(t2, s2);
Rewrite(t2);
Assign(t1, s1);
Rewrite(t1);
writeln(t2, s);
Close(t2);
p := Pos(#13#10#13#10, s);
while p <> 0 do
begin
Delete(s, p, 2);
p := Pos(#13#10#13#10, 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 имя
временного файла дополняется префиксом #, что также гарантирует его отличие от имени основного файла задания.
При реализации задания на обработку текстовых файлов для генерации содержимого файла используются
функции TextCount и TextSample. Строка, возвращаемая функцией TextSample,
представляет собой текст, содержащий маркеры конца строки символы #13#10. Указанные символы
разделяют соседние строки текста (в конце текста маркер конца строки не указывается).
Благодаря наличию маркеров конца строки полученный текст можно записать в текстовый файл с помощью единственной процедуры
writeln, которая, кроме записи текста, обеспечивает добавление маркера конца строки в конец файла.
После разработки новых заданий необходимо изменить количество заданий в вызове процедуры CreateGroup на 7 и
включить вызовы новых процедур в основную процедуру группы InitTask:
procedure InitTask(num: integer);
begin
case num of
1..2: UseTask('Begin', num);
3: MakerDemo3;
4: MakerDemo4;
5: MakerDemo5;
6: MakerDemo6;
7: MakerDemo7;
end;
end;
Приведем вид окна задачника для новых заданий:
Добавление заданий на обработку динамических структур данных
Наконец, добавим в нашу группу задание,
посвященное обработке динамических структур данных, причем представим его в двух
вариантах: традиционном, основанном на использовании записей типа TNode
и связанных с ними указателей типа PNode, и «объектном», характерном для .NET-языков.
Следует подчеркнуть, что при разработке как традиционного, так и объектного варианта заданий
на динамические структуры надо использовать типы TNode и PNode и связанные с ними процедуры конструктора учебных заданий.
В то же время, при выполнении объектного варианта задания на одном из .NET-языков требуется
использовать объекты типа Node (которые при разработке задания не применяются).
Задание, которое мы реализуем, дублирует задание Dynamic30, посвященное преобразованию
односвязного списка в двусвязный (подгруппа «Динамические структуры данных: двусвязный список»).
Оформим два варианта этого задания в виде процедур MakerDemo8 и MakerDemo8Net:
var WrongNode: TNode;
procedure MakerDemo8Data;
var
i, n: integer;
p, p1, p2: PNode;
begin
if Random(4) = 0 then
n := 1
else
n := 2 + Random(8);
new(p1);
p1^.Data := Random(90) + 10;
p1^.Prev := nil;
p2 := p1;
for i := 2 to n do
begin
new(p);
p^.Data := Random(90) + 10;
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 MakerDemo8Net;
begin
CreateTask('Динамические структуры данных: двусвязный список');
TaskText('Дана ссылка~{A}_1 на начало непустой цепочки ' +
'элементов-объектов типа Node,', 0, 1);
TaskText('связанных между собой с помощью своих свойств Next. ' +
'Используя свойства Prev', 0, 2);
TaskText('данных объектов, преобразовать исходную ' +
'(\Iодносвязную\i) цепочку в \Iдвусвязную\i,', 0, 3);
TaskText('в которой каждый элемент связан не только ' +
'с последующим элементом (с помощью', 0, 4);
TaskText('свойства Next), но и с предыдущим (с помощью ' +
'свойства Prev). Свойство Prev', 0, 5);
TaskText('первого элемента положить равным \O. Вывести ' +
'ссылку~{A}_2 на последний', 0, 0);
TaskText('элемент преобразованной цепочки.', 0, 0);
SetObjectStyle;
MakerDemo8Data;
end;
Анализируя приведенные варианты процедур, легко заметить, что они отличаются лишь деталями формулировки задания.
Алгоритмы генерации исходных и контрольных данных
для традиционного и объектного вариантов совпадают, поэтому они выделены в отдельную вспомогательную процедуру MakerDemo8Data.
В то же время представления динамических структур и связанных с ними
указателей или объектов будут отличаться (см. рисунки, приведенные ниже). Необходимые корректировки в представлении
динамических структур выполняются задачником автоматически, с учетом используемого языка программирования.
Однако для
языка PascalABC.NET требуемую настройку необходимо выполнить явно, так как в нем можно использовать оба варианта
представления динамических структур: традиционный (как для обычного Паскаля в системах Delphi и Free Pascal Lazarus)
и объектный (как в языках C# и Visual Basic .NET).
Для того чтобы представление динамических данных при выполнении задания в среде PascalABC.NET соответствовало объектному
варианту, следует в начале процедуры, реализующей задание (перед вызовом любых процедур, связанных
с указателями и динамическими структурами), вызвать специальную процедуру без параметров
SetObjectStyle. Для остальных языков данная процедура не выполняет никаких действий.
Обратите внимание на возможность использования в формулировке задания более 5 экранных строк. Строки, которые
не умещаются в области формулировки задания, следует добавлять к заданию процедурой TaskText,
указывая в качестве последнего параметра процедуры число 0. При наличии подобных строк в окне задачника слева от области формулировки появятся
кнопки, обеспечивающие прокрутку формулировки задания (кроме этих кнопок можно также использовать стандартные
клавиши, в частности, клавиши со стрелками).
Для того чтобы имя нулевого указателя (или объекта) соответствовало используемому языку программирования,
в формулировке задания применяются управляющие последовательности \N (имя нулевого указателя) и \O (имя нулевого объекта).
Для языка PascalABC.NET обе эти последовательности генерируют текст nil.
Достаточно часто алгоритмы, разработанные учащимися для обработки динамических структур данных, дают неверные результаты
в случае особых (хотя и допустимых) структур, например, состоящих только из одного элемента. Поэтому желательно
предусмотреть появление подобных структур в тестовых наборах исходных данных. В наших заданиях исходный список, состоящий из
одного элемента, будет предлагаться программе учащегося в среднем один раз при каждых четырех тестовых испытаниях.
При формировании односвязной структуры неиспользуемые поля Prev для каждого элемента структуры следует
положить равными адресу «фиктивного» элемента (в нашем случае переменной WrongNode),
не связанного с данной структурой. Заметим, что для всех элементов, кроме первого, можно было бы положить
значения поля Prev равными nil, однако это не подходит для
первого элемента: если поле Prev первого элемента будет равно nil, то слева от него будет выведен
«лишний» (в данной ситуации) текст nil<.
Характерной особенностью разработки заданий на динамические структуры является обратный порядок создания этих структур:
вначале создаются контрольные структуры (которые сразу передаются в задачник), а затем они преобразуются в соответствующие
исходные структуры, которые должны не только передаваться в задачник, но и оставаться в памяти, чтобы в дальнейшем их можно было
использовать в программе учащегося, выполняющей это задание.
Если в группу включаются задания на динамические структуры, то необходимо анализировать текущий язык программирования,
используемый задачником. Это обусловлено двумя причинами:
- имеется язык, не поддерживающий работу с динамическими структурами (Visual Basic);
- в языках платформы .NET необходимо использовать «объектный» стиль формулировок вместо
традиционного стиля, основанного на указателях и применяемого для языков Pascal и C++.
Кроме того, следует определиться с выбором стиля для языка PascalABC.NET, поскольку в нем
можно использовать как стиль указателей, так и стиль объектов. Можно, например, включить в группу заданий для языка
PascalABC.NET оба варианта каждого задания.
Отмеченные обстоятельства приводят к тому, что для разных языков программирования создаваемая группа
может содержать разное число заданий и, кроме того, для этих заданий будут использоваться разные
инициализирующие процедуры.
С учетом этих замечаний изменим основную процедуру группы InitTask следующим образом:
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
MakerDemo8net;
9: if CurrentLanguage = lgPascalABCNET then
MakerDemo8Net;
end;
end;
В этой процедуре используется функция CurrentLanguage, позволяющая определить текущий язык программирования,
используемый задачником. Если текущий язык относится к категории языков, поддерживающих указатели (в том числе PascalABC.NET), то
в качестве задания номер 8 вызывается процедура MakerDemo8, в которой задание формулируется в терминах указателей.
В противном случае вызывается вариант задания для .NET-языков, использующий объектную терминологию. Наконец,
если текущим языком является PascalABC.NET, то для него будет
доступно дополнительное задание номер 9, представляющее собой «объектный» вариант задания номер 8.
Функцию CurrentLanguage потребуется использовать и в начале процедуры inittaskgroup для того, чтобы
правильно определить количество заданий в группе для разных языков программирования (обратите внимание на то, что теперь
в качестве предпоследнего параметра процедуры CreateGroup используется переменная n):
var
n: integer;
begin
case CurrentLanguage of
lgVB: n := 7;
lgPascalABCNET: n := 9;
else n := 8;
end;
CreateGroup('MakerDemo', 'Примеры различных задач',
'М. Э. Абрамян, 2009', 'qwqfsdf13dfttd', n, InitTask);
Новые задания следует протестировать для различных языков программирования. Для этого можно воспользоваться еще
одной новой возможностью программы 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('указатели (Pascal, C++), и .NET-языков.');
CommentText('Для языка Visual Basic это задание недоступно.');
CommentText('В системе PascalABC.NET доступны оба варианта задания.');
Приведем заключительную часть html-страницы с описанием данной группы после установки C# в качестве текущего языка:
Особенности оформления группы заданий в виде модуля системы PascalABC.NET
При добавлении новой группы заданий в задачник, интегрированный в систему PascalABC.NET,
вместо dll-файла можно использовать pcu-файл, т. е. откомпилированный модуль языка PascalABC.NET.
Однако в этом случае созданную группу нельзя будет включить в универсальный вариант задачника.
Для оформления описанной выше группы MakerDemo в виде pcu-файла
достаточно изменить файл PT4MakerDemo.dpr следующим образом:
- замените расширение данного файла на .pas;
- в первой строке файла (
library PT4MakerDemo;) замените слово library на unit;
- в заголовке процедуры InitTask (
procedure InitTask(num: integer); stdcall;)
удалите модификатор stdcall вместе с завершающим символом «;»;
- в конец процедуры inittaskgroup добавьте оператор
RegisterGroup('PT4MakerDemo');
- удалите строку, начинающуюся словом
exports и добавьте в завершающий блок beginend вызов функции
inittaskgroup.
Завершающий фрагмент файла (после описания функции inittaskgroup) должен иметь вид:
begin
inittaskgroup;
end.
Таким образом, основное отличие нового варианта состоит в удалении раздела exports и
включении вызова процедуры inittaskgroup в секцию инициализации модуля PT4MakerDemo.
Вызов процедуры RegisterGroup, добавленный в конец процедуры inittaskgroup,
позволяет зарегистрировать созданную группа заданий в
модуле PT4Load, интегрированном в систему PascalABC.NET.
Для того чтобы созданная группа была доступна для использования, необходимо, чтобы файл PT4MakerDemo.pcu
размещался либо в рабочем каталоге учащегося, либо в подкаталоге Lib системного каталога PascalABC.NET.
При этом в заготовке для выполнения заданий из этой группы потребуется дополнительно указать имя этого модуля,
например:
uses PT4, PT4MakerDemo;
begin
Task('MakerDemo2');
end.
После первого запуска программы, подключившей модуль PT4MakerDemo (т. е. после того, как
хотя бы один раз будет успешно выполнена функция RegisterGroup), создавать подобную заготовку
можно будет с помощью программного модуля PT4Load.
В отличие от групп заданий, реализованных в виде dll-файлов, группы в pcu-файлах не распознаются
программным модулем PT4Demo, интегрированным в систему PascalABC.NET. Следует также иметь в виду, что если
группа заданий с одним и тем же именем реализована и в виде dll-файла и в виде pcu-файла,
то программный модуль PT4Load использует dll-вариант группы как более универсальный
(при этом к программе учащегося подключается только модуль PT4).
В подобной ситуации для выбора pcu-варианта группы достаточно
указать имя соответствующего pcu-модуля в списке uses (например, uses PT4, PT4MakerDemo;).
|