808 lines
57 KiB
HTML
808 lines
57 KiB
HTML
<html>
|
||
|
||
<head>
|
||
<object type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e">
|
||
<param name="Keyword" value="">
|
||
</object>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
|
||
<title></title>
|
||
<link rel="StyleSheet" href="../../default.css">
|
||
</head>
|
||
|
||
<body>
|
||
<H1>Модуль PT4TaskMakerNET: примеры разработки учебных заданий</H1>
|
||
<hr>
|
||
|
||
<h2>Создание простейшей сводной группы</h2>
|
||
<p>Вначале опишем действия по созданию наиболее простого варианта группы заданий — <a href="tmaker_main.html#Libr"><i>сводной группы</i></a>,
|
||
в которой не разрабатываются новые задания, а лишь производится перекомпоновка заданий из имеющихся групп.
|
||
</p>
|
||
|
||
<p>Создадим группу
|
||
заданий MakerDemo, в которую импортируем два первых задания из базовой группы Begin. Следуя правилам об именовании
|
||
dll-файлов с группами заданий, дадим нашей библиотеке имя PT4MakerDemo.
|
||
</p>
|
||
|
||
|
||
<p>Файл PT4MakerDemo.pas, содержащий сводную группу заданий, является кратким и имеет стандартную структуру:
|
||
</p>
|
||
|
||
<pre>library PT4MakerDemo;
|
||
|
||
uses PT4TaskMakerNET;
|
||
|
||
procedure InitTask(num: integer);
|
||
begin
|
||
case num of
|
||
1..2: UseTask('Begin', num);
|
||
end;
|
||
end;
|
||
|
||
procedure inittaskgroup;
|
||
begin
|
||
CreateGroup('MakerDemo', 'Примеры различных задач',
|
||
'М. Э. Абрамян, 2013', 'qwqfsdf13dfttd', 2, InitTask);
|
||
end;
|
||
|
||
procedure activate(S: string);
|
||
begin
|
||
ActivateNET(S);
|
||
end;
|
||
|
||
begin
|
||
end.
|
||
</pre>
|
||
|
||
<p>К библиотеке подключается модуль PT4TaskMakerNET, после чего в ней описывается основная процедура группы заданий
|
||
<a href="tmaker_proc1.html#TInitTaskProc">InitTask</a>, определяющая задание по его номеру.
|
||
Поскольку мы не создавали своих заданий, в данной процедуре
|
||
используется только стандартная процедура <a href="tmaker_proc1.html#UseTask">UseTask</a>, позволяющая импортировать задания из имеющихся групп.
|
||
В нашем случае импортируются задания с номерами 1 и 2 из группы Begin.
|
||
</p>
|
||
<p>Затем описывается процедура инициализации данной группы заданий. Она должна иметь стандартное имя inittaskgroup
|
||
(<i><b>набранное строчными, т. е. маленькими буквами</b></i>). В этой процедуре
|
||
вызывается процедура <a href="tmaker_proc1.html#CreateGroup">CreateGroup</a>, в которой задаются настройки создаваемой группы:
|
||
имя (<code>'MakerDemo'</code>), описание (<code>'Примеры различных задач'</code>), сведения об авторе,
|
||
строковый ключ, число заданий (<code>2</code>) и основная процедура группы (<code>InitTask</code>).
|
||
</p>
|
||
<p>После процедуры inittaskgroup описывается вспомогательная процедура activate
|
||
(ее имя также должно быть набрано строчными буквами), в которой необходимо вызвать
|
||
процедуру ActivateNET, описанную в модуле PT4TaskMakerNET.
|
||
</p>
|
||
|
||
<a name="debug"></a>
|
||
<h2>Тестирование созданной группы</h2>
|
||
|
||
<p>Для успешной компиляции программы с созданной группой необходимо, чтобы ей был доступен
|
||
модуль PT4TaskMakerNET. Этот модуль входит в число стандартных модулей библиотеки системы PascalABC.NET
|
||
и размещается в подкаталоге LIB системного каталога PascalABC.NET, поэтому копировать его в рабочий каталог
|
||
не требуется.
|
||
Однако даже при успешной компиляции программы просмотреть задания группы не удастся, так как
|
||
созданную библиотеку (dll-файл) нельзя запускать на выполнение (при успешной компиляции будет выведено
|
||
сообщение «Невозможно запустить динамическую библиотеку».
|
||
</p>
|
||
<p>Для тестирования полученной библиотеки
|
||
необходимо создать вспомогательную программу, являющуюся заготовкой для выполнения заданий из созданной группы.
|
||
Так как после успешной компиляции библиотеки в рабочем каталоге уже содержится файл PT4MakerDemo.dll,
|
||
для создания программы-заготовки можно использовать программный модуль PT4Load.
|
||
Вызвав его окно на экран (для этого достаточно использовать клавиатурную комбинацию [Shift]+[Ctrl]+[L])
|
||
и удалив, при необходимости, имя ранее введенного задания,
|
||
мы должны увидеть в списке доступных групп заданий созданную нами группу MakerDemo.
|
||
Если имя группы MakerDemo не отображается, значит, задачник не смог успешно загрузить эту группу
|
||
из библиотеки PT4MakerDemo.dll. В этом случае необходимо проверить имя созданной библиотеки (в частности,
|
||
наличие в нем префикса PT4) и наличие в файле библиотеки процедур inittaskgroup и activate,
|
||
определенных по описанным выше правилам.
|
||
</p>
|
||
<p>Если имя группы появилось в списке, то надо ввести в поле «Задание» имя «MakerDemo1»
|
||
и нажать клавишу [Enter] (или кнопку «Загрузка»); в результате будет создан файл MakerDemo1.pas,
|
||
который сразу загрузится в редактор среды PascalABC.NET. Приведем содержимое этого файла:
|
||
</p>
|
||
<pre>
|
||
uses PT4;
|
||
|
||
begin
|
||
Task('MakerDemo1');
|
||
|
||
end.
|
||
</pre>
|
||
<p>Поскольку мы собираемся просматривать задания группы в демо-режиме,
|
||
добавим в конец строки с именем
|
||
задания символ «?»:
|
||
</p>
|
||
<pre> Task('MakerDemo1?');
|
||
</pre>
|
||
<p>После компиляции и запуска полученной программы на экране отобразится окно задачника
|
||
с указанным заданием данной группы:
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4make6.png">
|
||
</p>
|
||
<p>По умолчанию окно задачника отображается в режиме с динамической компоновкой, который появился
|
||
в версии 4.11 и является более наглядным, чем режим с фиксированной компоновкой. Однако
|
||
при разработке заданий желательно применять режим с фиксированной компоновкой, поскольку
|
||
он позволит выявить недостатки форматирования (в частности, вертикального выравнивания данных),
|
||
присущие только этому режиму. Для переключения между режимами отображения данных достаточно нажать клавишу
|
||
[F4]. После выполнения этого действия окно задачника изменится следующим образом:
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4make6a.png">
|
||
</p>
|
||
|
||
<p>В окне задачника можно просматривать все имеющиеся задания данной группы
|
||
(нажимая клавиши [Enter] и [Backspace], а также генерировать различные варианты исходных данных
|
||
и связанных с ними контрольных (т. е. «правильных») результатов. При закрытии окна
|
||
программа немедленно завершит работу, и мы вернемся в редактор среды PascalABC.NET.
|
||
Заметим, что при последующих запусках программы будет автоматически выбираться тот режим окна
|
||
задачника, в котором оно находилось в момент его предшествующего закрытия.
|
||
</p>
|
||
<p>
|
||
</p>
|
||
<p><b>Примечание</b>. После добавления в группу нового задания было бы желательно, чтобы
|
||
при звпуске тестирующей программы на экране сразу отображались данные, связанные с последним
|
||
добавленным заданием. Чтобы не приходилось каждый раз изменять номер задания в процедуре Task,
|
||
можно удалить этот номер, указав символ «?» сразу после имени группы:
|
||
<code>Task('MakerDemo?')</code>.
|
||
В этом случае при запуске программы на экране будет отображаться
|
||
<i>последнее</i> задание данной группы.
|
||
</p>
|
||
|
||
<a name="comments"></a><h2>Добавление описания группы и ее подгрупп</h2>
|
||
|
||
<p>По тексту, расположенному выше названия задания MakerDemo1 (см. приведенные выше рисунки),
|
||
мы видим, что импортированные из группы Begin задания
|
||
входят в подгруппу с заголовком «Ввод и вывод данных, оператор присваивания».
|
||
В сводной группе MakerDemo мы можем добавить комментарий (<i>преамбулу</i>) как к самой группе, так и к любой имеющейся в ней
|
||
подгруппе. Кроме того, мы можем <i>импортировать</i>
|
||
преамбулу любой имеющейся группы или подгруппы. Для иллюстрации этих возможностей добавим в процедуру inittaskgroup
|
||
новые операторы (их надо указать после вызова процедуры CreateGroup):
|
||
</p>
|
||
|
||
<pre>CommentText('Данная группа демонстрирует различные возможности');
|
||
CommentText('\Iконструктора учебных заданий\i \MPT4TaskMaker\m.');
|
||
|
||
Subgroup('Ввод и вывод данных, оператор присваивания');
|
||
CommentText('В этой подгруппе содержатся задания, импортированные');
|
||
CommentText('из группы Begin.\PПриводимый ниже абзац преамбулы');
|
||
CommentText('также импортирован из данной группы.\P');
|
||
UseComment('Begin');
|
||
</pre>
|
||
<p>Два первых вызова процедуры <a href="tmaker_proc1.html#CommentText">CommentText</a>
|
||
определяют текст преамбулы для группы MakerDemo. Обратите внимание на
|
||
<a href="tmaker_format.html"><i>управляющие последовательности</i></a>: пара последовательностей <code>\I</code>
|
||
и <code>\i</code> выделяет <i>курсивный</i> фрагмент,
|
||
а пара <code>\M</code> и <code>\m</code> выделяет фрагмент, в которым используется <code>моноширинный</code> шрифт.
|
||
Последующий вызов процедуры Subgroup устанавливает режим определения преамбулы для подгруппы с указанным именем.
|
||
В тексте этой преамбулы, который, как и текст преамбулы группы, определяется с помощью процедуры CommentText,
|
||
используется управляющая последовательность <code>\P</code>, обеспечивающая переход к новому абзацу.
|
||
</p>
|
||
<p>Наконец, последняя процедура (<a href="tmaker_proc1.html#UseComment">UseComment</a>) импортирует преамбулу группы
|
||
Begin в преамбулу нашей подгруппы «Ввод и вывод данных, оператор присваивания». Имеется также вариант процедуры
|
||
UseComment, позволяющий импортировать преамбулу подгруппы; в этом варианте следует указать два параметра: имя группы и
|
||
заголовок требуемой подгруппы, входящей в эту группу. Импортировать преамбулы подгрупп можно только для тех групп заданий, в которых
|
||
имеется разделение на подгруппы (обычно это группы, содержащие большое количество заданий). В группе Begin деления на подгруппы нет, поэтому
|
||
из нее можно импортировать только преамбулу самой группы.
|
||
</p>
|
||
<p>Для того чтобы ознакомиться с результатом сделанных изменений, следует сгенерировать html-страницу с текстом группы MakerDemo.
|
||
Для этого достаточно внести небольшое изменение в тестирующую программу, а именно, следует
|
||
заменить символ «?» в параметре процедуры Task на «#»: <code>Task('MakerDemo?')</code>.
|
||
Теперь при запуске данной программы на экране вместо окна задачника появится html-браузер с описанием созданной группы:
|
||
|
||
|
||
<p class="ris"><img border="1" src="images\pt4make7.png">
|
||
</p>
|
||
<p>Обратите внимание на последний абзац в описании подгруппы («Все входные и выходные данные в заданиях этой группы являются
|
||
вещественными числами»), который был импортирован из группы Begin.
|
||
</p>
|
||
|
||
|
||
<p><b>Примечание</b>. Если указать в параметре процедуры Task символ «#»,
|
||
не удаляя номер задания (например, <code>Task('MakerDemo2#')</code>), то в html-описание
|
||
будет включено только задание с указанным номером. При этом будут также выведены комментарии ко всей группе и к той подгруппе, к
|
||
которой относится выбранное задание. Для включения в html-страницу нескольких заданий (или групп заданий) достаточно для каждого из них
|
||
вызвать процедуру Task с параметром, оканчивающимся символом «#».
|
||
</p>
|
||
|
||
|
||
|
||
<h2>Добавление нового задания</h2>
|
||
<p>Добавим к нашей группе новое задание. Фактически это задание будет дублировать задание Begin3,
|
||
однако вместо импортирования этого задания мы разработаем его самостоятельно. Все действия по созданию
|
||
нового задания удобно реализовать во вспомогательной процедуре, которую можно назвать MakerDemo3
|
||
(таким образом, название процедуры будет соответствовать имени создаваемого задания, хотя это и не является обязательным):
|
||
</p>
|
||
<pre>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;
|
||
</pre>
|
||
<p>Описание процедуры MakerDemo3 (как и описания всех других процедур, обеспечивающих формирование новых заданий)
|
||
следует разместить перед описанием процедуры InitTask.
|
||
</p>
|
||
<p>Процедура MakerDemo3 включает все основные действия, используемые при формировании нового задания:
|
||
</p>
|
||
<ul><li><i>инициализацию</i> нового задания (процедура <a href="tmaker_proc1.html#CreateTask">CreateTask</a>;
|
||
мы указали в этой процедуре, что данное задание должно входить
|
||
в подгруппу «Ввод и вывод данных, оператор присваивания», т. е. в ту же подгруппу, что и два
|
||
предыдущих задания);
|
||
<li>определение его <i>формулировки</i> (процедуры <a href="tmaker_proc1.html#TaskText">TaskText</a>; обратите внимание на используемые в
|
||
этих процедурах <a href="tmaker_format.html">управляющие последовательности</a>);
|
||
<li>определение исходных (процедуры <a href="tmaker_proc1.html#DataN">DataR</a>) и результирующих данных
|
||
(процедуры <a href="tmaker_proc1.html#ResultN">ResultR</a>); при этом исходные данные генерируются с помощью
|
||
датчика случайных чисел (процедура <a href="tmaker_proc1.html#Random">RandomN</a>);
|
||
<li>указание количества успешных тестовых запусков программы учащегося, достаточных для регистрации задания как выполненного
|
||
(процедура <a href="tmaker_proc1.html#SetTestCount">SetTestCount</a>;
|
||
для нашего простого задания достаточно трех <i>проведенных подряд</i> успешных тестовых запусков).
|
||
</ul>
|
||
<p>Необходимо также включить вызов созданной процедуры в основную процедуру группы MakerDemo, связав его с номером 3:
|
||
</p>
|
||
<pre>procedure InitTask(num: integer);
|
||
begin
|
||
case num of
|
||
1..2: UseTask('Begin', num);
|
||
3: MakerDemo3;
|
||
end;
|
||
end;
|
||
</pre>
|
||
<p>Наконец, следует откорректировать число заданий в вызове процедуры CreateGroup, изменив его на 3.
|
||
</p>
|
||
<p>Запустив тестирующую программу, мы увидим в html-описании группы MakerDemo формулировки трех заданий,
|
||
а выполнив обратную замену в этой программе символа «#» на «?» (в результате
|
||
вызов процедуры Task опять примет вид <code>Task('MakerDemo?')</code>) и повторно запустив программу
|
||
на выполнение, мы увидим окно задачника
|
||
с загруженным заданием MakerDemo3. Заметим, что при последующих запусках проекта мы будем получать
|
||
в окне задачника различные исходные данные; это связано с тем, что при генерации исходных данных
|
||
используется датчик случайных чисел.
|
||
</p>
|
||
|
||
|
||
|
||
<h2>Добавление заданий на обработку двумерных массивов и символьных строк</h2>
|
||
<p>Добавим к группе MakerDemo еще два задания: первое из них дублирует задание Matrix7
|
||
(подгруппа «Двумерные массивы (матрицы): вывод элементов»), а второе
|
||
не имеет полного аналога в группе String, однако может быть отнесено к ее первой
|
||
подгруппе: «Символы и строки: основные операции». Реализуем эти задания
|
||
в процедурах MakerDemo4 и MakerDemo5:
|
||
</p>
|
||
<pre>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;
|
||
</pre>
|
||
<p>Обратите внимание на использование вспомогательной функции <a href="tmaker_proc1.html#Center">Center</a>
|
||
для центрирования строк матрицы в области
|
||
исходных и результирующих данных: каждый элемент матрицы занимает 5 экранных позиций, а между
|
||
элементами размещается по одному пробелу. В процедуре MakerDemo4 не вызывается процедура SetTestCount; в этом случае
|
||
число успешных тестов, необходимых для регистрации задания как выполненного, по умолчанию полагается равным 5.
|
||
</p>
|
||
<p>При выводе элементов исходной матрицы и результирующей матричной строки дополнительные комментарии
|
||
указывать не требуется, поэтому используется вариант процедур DataR и ResultR, в котором комментарий
|
||
отсутствует (этот вариант процедур групп Data и Result добавлен в версию 4.11 конструктора учебных заданий).
|
||
</p>
|
||
<p>В процедуре MakerDemo5 для получения исходных символьных строк используются
|
||
функции <a href="tmaker_proc1.html#Sample">WordCount и WordSample</a>. С помощью этих функций можно получать
|
||
различные варианты русских слов. Заметим, что в конструкторе PT4TaskMaker имеются также функции
|
||
EnWordCount и EnWordSample, с помощью которых можно получать варианты английских слов.
|
||
</p>
|
||
<p>В процедуре MakerDemo5 использована еще одна возможность, появившаяся в версии 4.11 конструктора:
|
||
функция <a href="tmaker_proc1.html#CurrentTest">CurrentTest</a>,
|
||
возвращающая порядковый номер текущего тестового запуска. Использование этой функции позволяет
|
||
связать какой-либо особый вариант теста с некоторым номером тестового испытания, и тем самым гарантировать,
|
||
что программа с решением задачи обязательно будет проверена на этом особом варианте теста.
|
||
В нашем случае строка S выбирается из набора слов-образцов, среди которых имеется
|
||
сравнительно большое число слов, начинающихся и оканчивающихся одной и той же буквой.
|
||
Для более надежного тестирования решения желательно гарантировать, что в наборе тестов
|
||
будет <i>хотя бы один тест</i>, в котором начальный и конечный символ исходной строки различаются.
|
||
Разумеется, можно было бы <i>всегда</i> выбирать подобные строки, используя соответствующий
|
||
цикл while. Однако при наличии функции CurrentTest
|
||
в этом нет необходимости: достаточно выполнять подобный цикл для единственного теста, например,
|
||
с номером 3, как это сделано в приведенной реализации задания.
|
||
В дальнейшем мы рассмотрим более содержательный пример использования функции CurrentTest.
|
||
</p>
|
||
<p>Осталось изменить количество заданий в вызове процедуры CreateGroup на 5 и
|
||
включить вызовы новых процедур в основную процедуру группы InitTask:
|
||
</p>
|
||
<pre>procedure InitTask(num: integer);
|
||
begin
|
||
case num of
|
||
1..2: UseTask('Begin', num);
|
||
3: MakerDemo3;
|
||
4: MakerDemo4;
|
||
5: MakerDemo5;
|
||
end;
|
||
end;
|
||
</pre>
|
||
|
||
<p>Приведем вид окна задачника для новых заданий:
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4make8.png">
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4make9.png">
|
||
</p>
|
||
|
||
|
||
|
||
<h2>Добавление заданий на обработку файлов</h2>
|
||
<p>Добавим к группе MakerDemo еще два задания: первое из них дублирует задание File63
|
||
(подгруппа «Символьные и строковые файлы»), а второе
|
||
— задание Text16 (подгруппа «Текстовые файлы: основные операции»). Реализуем эти задания
|
||
в процедурах MakerDemo6 и MakerDemo7:
|
||
</p>
|
||
|
||
<pre>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;
|
||
</pre>
|
||
<p>При реализации этих заданий используется вспомогательная функция FileName(Len), позволяющая
|
||
создать случайное имя файла длины Len (без расширения).
|
||
Имя файла при этом будет содержать только цифры и строчные (маленькие) латинские буквы.
|
||
</p>
|
||
<p>Имена файлов, полученные с помощью функции FileName, дополняются расширением .tst (заметим, что
|
||
в базовых группах File, Text и Param это расширение используется в именах всех исходных и результирующих файлов).
|
||
</p>
|
||
<p>Функция FileName используется также для генерации элементов строкового файла в процедуре MakerDemo6.
|
||
</p>
|
||
<p>Для того чтобы предотвратить возможность случайного совпадения имен файлов, в процедуре MakerDemo6 к созданным именам
|
||
добавляются префиксы: <code>1</code> для первого файла, <code>2</code> для второго, <code>3</code> для третьего.
|
||
В процедуре MakerDemo7 имя
|
||
временного файла дополняется префиксом <code>#</code>, что также гарантирует его отличие от имени основного файла задания.
|
||
</p>
|
||
<p>В процедуре MakerDemo6 использован новый вариант <a href="tmaker_proc1.html#TaskText">процедуры TaskText</a>,
|
||
появившийся в версии 4.11 задачника.
|
||
В этом варианте процедура TaskText принимает один строковый параметр, который определяет
|
||
<i>всю формулировку задания</i>, причем в качестве разделителей строк, входящих в формулировку, можно использовать
|
||
символы #13, #10 или их комбинацию #13#10 (в указанном порядке).
|
||
Новый вариант процедуры TaskText позволяет более наглядно отобразить формулировку задания и
|
||
не требует указания дополнительных параметров.
|
||
</p>
|
||
<p>При реализации задания на обработку текстовых файлов для генерации содержимого файла используются
|
||
функции <a href="tmaker_proc1.html#Sample">TextCount и TextSample</a>. Строка, возвращаемая функцией TextSample,
|
||
представляет собой текст, содержащий <i>маркеры конца строки</i> — символы #13#10. Указанные символы
|
||
<i>разделяют соседние строки текста</i> (в конце текста маркер конца строки не указывается).
|
||
Благодаря наличию маркеров конца строки полученный текст можно записать в текстовый файл с помощью единственной процедуры
|
||
writeln, которая, кроме записи текста, обеспечивает добавление маркера конца строки в конец файла.
|
||
</p>
|
||
<p>После разработки новых заданий необходимо изменить количество заданий в вызове процедуры CreateGroup на 7 и
|
||
включить вызовы новых процедур в основную процедуру группы InitTask:
|
||
</p>
|
||
<pre>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;
|
||
</pre>
|
||
|
||
<p>Приведем вид окна задачника для новых заданий:
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4make10.png">
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4make11.png">
|
||
</p>
|
||
|
||
<a name="dynamic"></a><h2>Добавление заданий на обработку динамических структур данных</h2>
|
||
|
||
<p>Наконец, добавим в нашу группу задание,
|
||
посвященное обработке динамических структур данных, причем представим его в двух
|
||
вариантах: традиционном, основанном на использовании записей типа TNode
|
||
и связанных с ними указателей типа PNode, и «объектном», характерном для .NET-языков
|
||
(C#, Visual Basic .NET, PascalABC.NET), а также языков Python и Java.
|
||
Следует подчеркнуть, что при <i>разработке</i> как традиционного, так и объектного варианта заданий
|
||
на динамические структуры надо использовать типы TNode и PNode и связанные с ними процедуры конструктора учебных заданий.
|
||
В то же время, при <i>выполнении</i> объектного варианта задания на соответствующем языке требуется
|
||
использовать объекты типа Node (которые при разработке задания не применяются).
|
||
</p>
|
||
<p>Задание, которое мы реализуем, дублирует задание Dynamic30, посвященное преобразованию
|
||
односвязного списка в двусвязный (подгруппа «Динамические структуры данных: двусвязный список»).
|
||
Оформим два варианта этого задания в виде процедур MakerDemo8 и MakerDemo8Obj:
|
||
</p>
|
||
|
||
<pre>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;
|
||
</pre>
|
||
|
||
<p>Анализируя приведенные варианты процедур, легко заметить, что они отличаются лишь деталями формулировки задания.
|
||
Алгоритмы генерации исходных и контрольных данных
|
||
для традиционного и объектного вариантов совпадают, поэтому они выделены в отдельную вспомогательную процедуру MakerDemo8Data.
|
||
В то же время <i>представления</i> динамических структур и связанных с ними
|
||
указателей или объектов будут отличаться (см. рисунки, приведенные ниже). Необходимые корректировки в представлении
|
||
динамических структур выполняются задачником автоматически, с учетом используемого языка программирования.
|
||
</p>
|
||
<p>Однако для
|
||
языка PascalABC.NET требуемую настройку необходимо выполнить явно, так как в нем можно использовать оба варианта
|
||
представления динамических структур: традиционный (как для обычного Паскаля в системах Delphi и Free Pascal Lazarus)
|
||
и объектный (как в языках C#, Visual Basic .NET, Python и Java).
|
||
Для того чтобы представление динамических данных при выполнении задания в среде PascalABC.NET соответствовало объектному
|
||
варианту, следует в начале процедуры, реализующей задание (перед вызовом любых процедур, связанных
|
||
с указателями и динамическими структурами), вызвать специальную процедуру без параметров
|
||
<a href="tmaker_proc2.html#SetObjectStyle">SetObjectStyle</a>. Для остальных языков данная процедура не выполняет никаких действий.
|
||
</p>
|
||
<p>Обратите внимание на возможность использования в формулировке задания более 5 экранных строк. Строки, которые
|
||
не умещаются в области формулировки задания, следует добавлять к заданию процедурой TaskText,
|
||
указывая в качестве последнего параметра процедуры число 0 (см. процедуру MakerDemo8).
|
||
Еще проще задавать «длинные» формулировки заданий с помощью нового варианта
|
||
процедуры TaskText с единственным строковым параметром, содержащим все строки формулировки (см. процедуру MakerDemo9).
|
||
При наличии подобных строк в окне задачника (если окно находится в режиме с фиксированной компоновкой)
|
||
слева от области формулировки появятся кнопки, обеспечивающие прокрутку формулировки задания;
|
||
кроме этих кнопок для прокрутки можно также использовать стандартные клавиши, в частности, клавиши со стрелками.
|
||
</p>
|
||
<p>Для того чтобы имя нулевого указателя (или объекта) соответствовало используемому языку программирования,
|
||
в формулировке задания применяются управляющие последовательности <code>\N</code> (имя нулевого указателя) и <code>\O</code> (имя нулевого объекта).
|
||
Для языка PascalABC.NET обе эти последовательности генерируют текст <code>nil</code>.
|
||
</p>
|
||
<p>Достаточно часто алгоритмы, разработанные учащимися для обработки динамических структур данных, дают неверные результаты
|
||
в случае особых (хотя и допустимых) структур, например, состоящих только из одного элемента. Поэтому желательно
|
||
предусмотреть появление подобных структур в тестовых наборах исходных данных.
|
||
В наших заданиях исходный список, состоящий из
|
||
<i>одного</i> элемента, будет предлагаться программе учащегося в среднем один раз при каждых четырех тестовых испытаниях.
|
||
Кроме того, благодаря использованию функции <a href="tmaker_proc1.html#CurrentTest">CurrentTest</a>, появившейся в
|
||
версии 4.11 конструктора, вариант списка с единственным элементом будет предложен программе учащегося для обработки
|
||
в тесте номер 2, а вариант списка с более чем одним элементом — в тесте номер 4. Таким образом, можно гарантировать,
|
||
что при прохождении набора из 5 тестовых испытаний программе будут предложены как «стандартные»,
|
||
так и «особые» наборы исходных данных.
|
||
</p>
|
||
<p>При формировании односвязной структуры неиспользуемые поля Prev для каждого элемента структуры следует
|
||
положить равными адресу «фиктивного» элемента (в нашем случае — переменной WrongNode),
|
||
не связанного с данной структурой. Заметим, что для всех элементов, кроме первого, значения поля Prev
|
||
можно было бы положить равными nil, однако это не подходит для
|
||
первого элемента: если поле Prev первого элемента будет равно nil, то слева от него будет выведен
|
||
«лишний» (в данной ситуации) текст <code>nil<</code>.
|
||
</p>
|
||
<p>Характерной особенностью разработки заданий на динамические структуры является <i>обратный порядок</i> создания этих структур:
|
||
вначале создаются <i>контрольные</i> структуры (которые сразу передаются в задачник), а затем они преобразуются в соответствующие
|
||
<i>исходные</i> структуры, которые должны не только передаваться в задачник, но и <i>оставаться в памяти</i>, чтобы в дальнейшем их можно было
|
||
использовать в программе учащегося, выполняющей это задание.
|
||
</p>
|
||
|
||
<p>Если в группу включаются задания на динамические структуры, то необходимо <i>анализировать текущий язык программирования</i>,
|
||
используемый задачником. Это обусловлено двумя причинами:
|
||
</p>
|
||
<ul><li> имеются языки, для которых отсутствует возможность выполнять
|
||
задания на обработку динамических структур (например, Visual Basic и 1C);
|
||
<li> в языках платформы .NET, а также Python и Java, необходимо использовать «объектный» стиль формулировок вместо
|
||
стиля, основанного на указателях и применяемого для языков Pascal и C++.
|
||
</ul>
|
||
<p>Кроме того, следует определиться с выбором стиля для языка PascalABC.NET, поскольку в нем
|
||
можно использовать как стиль указателей, так и стиль объектов. Можно, например, включить в группу заданий для языка
|
||
PascalABC.NET оба варианта каждого задания.
|
||
</p>
|
||
<p>Отмеченные обстоятельства приводят к тому, что для разных языков программирования создаваемая группа
|
||
может содержать разное число заданий и, кроме того, для этих заданий будут использоваться разные
|
||
инициализирующие процедуры.
|
||
</p>
|
||
<p>С учетом этих замечаний изменим основную процедуру группы InitTask следующим образом:
|
||
</p>
|
||
|
||
|
||
<pre>procedure InitTask(num: integer);
|
||
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;
|
||
</pre>
|
||
|
||
<p>В этой процедуре используется функция <a href="tmaker_proc1.html#CurrentLanguage">CurrentLanguage</a>,
|
||
позволяющая определить текущий язык программирования,
|
||
используемый задачником. Если текущий язык относится к категории языков,
|
||
поддерживающих указатели (в том числе PascalABC.NET), то
|
||
в качестве задания номер 8 вызывается процедура MakerDemo8, в которой задание формулируется в терминах указателей.
|
||
В противном случае вызывается вариант задания, использующий объектную терминологию.
|
||
При использовании языка PascalABC.NET число заданий в группе будет равно 9; при этом дополнительное
|
||
задание номер 9 будет представлять собой «объектный» вариант задания номер 8.
|
||
</p>
|
||
|
||
<p>Функцию CurrentLanguage потребуется использовать и в начале процедуры inittaskgroup для того, чтобы
|
||
правильно определить количество заданий в группе для разных языков программирования.
|
||
Приведем фрагмент, на который надо заменить вызов процедуры CreateGroup и предшествующее ему ключевое слово begin
|
||
(обратите внимание на то, что теперь
|
||
в качестве предпоследнего параметра процедуры CreateGroup используется переменная n):
|
||
</p>
|
||
<pre>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);
|
||
</pre>
|
||
<p>Приведенный набор условий будет правильно определять количество заданий и в случае,
|
||
если состав языков, поддерживаемых задачником, будет расширен. Это обеспечивается тем, что
|
||
в условиях используются не константы для конкретных языков (за исключением константы lgPascalABCNET),
|
||
а битовые маски lgWithPointers и lgWithObjects. Первая из этих масок включает все языки, для которых
|
||
в задачнике можно использовать варианты заданий на динамические структуры, основанные на указателях,
|
||
а вторая — все языки, позволяющие использовать варианты аналогичных заданий
|
||
в объектной терминологии.
|
||
</p>
|
||
|
||
<p>В среде PascalABC.NET можно протестировать оба варианта реализованного задания.
|
||
Приведем вид окна задачника для этого задания (первый рисунок соответствует
|
||
варианту задания, использующему указатели, второй — варианту, использующему объекты).
|
||
Обратите внимание
|
||
на кнопки, расположенные справа от формулировки задания и обеспечивающие ее прокрутку.
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4make12.png">
|
||
</p>
|
||
<p class="ris"><img border="0" src="images\pt4make13.png">
|
||
</p>
|
||
<p>Завершая оформление модуля PT4MakerDemo, добавим комментарии к новым подгруппам заданий (указанные операторы следует поместить
|
||
в конец процедуры inittaskgroup):
|
||
</p>
|
||
|
||
<pre> Subgroup('Двумерные массивы (матрицы): вывод элементов');
|
||
CommentText('Данное задание дублирует задание Matrix7.');
|
||
|
||
Subgroup('Символьные и строковые файлы');
|
||
CommentText('Данное задание дублирует задание File63.');
|
||
CommentText('Оно демонстрирует особенности, связанные с двоичными');
|
||
CommentText('\Iстроковыми\i файлами.');
|
||
|
||
Subgroup('Текстовые файлы: основные операции');
|
||
CommentText('Данное задание дублирует задание Text16.');
|
||
|
||
Subgroup('Динамические структуры данных: двусвязный список');
|
||
CommentText('Данное задание дублирует задание Dynamic30.');
|
||
CommentText('\PЗадание реализовано в двух вариантах: основанном на использовании указателей');
|
||
CommentText('(для языков Pascal и C++) и основанном на использовании объектов (для языков платформы .NET,');
|
||
CommentText('а также Python и Java). Для языка Visual Basic это задание недоступно.');
|
||
CommentText('В системе PascalABC.NET доступны оба варианта задания.');
|
||
</pre>
|
||
|
||
<p>Приведем заключительную часть html-страницы с описанием данной группы:
|
||
</p>
|
||
|
||
<p class="ris"><img border="1" src="images\pt4make14.png">
|
||
</p>
|
||
|
||
|
||
</body>
|
||
|
||
</html>
|
||
|
||
|