October 12

Разработка заданий для модуля LightPT

Начальные действия

Чтобы задания проверялись автоматически, необходимо создать Урок с заданиями. Урок представляет собой папку, в которой находятся .pas-файлы - заготовки с формулировками заданий, а также специальный файл lightpt.dat. Этот файл может быть пустым или содержать название урока. Именно этот файл является маркером того, что все задания в этой папки будут проходить через невидимую проверяющую систем

Итак, создадим папку Lesson1 и в ней файл lightpt.dat, в котором напишем одну строчку в кодировке Windows 1251: Урок-1. Задания на массивы

Создание задания

Теперь создадим в этой папке первый файл с заданием ArrTask1.pas со следующим содержимым:

Заметим, что в заготовке задания уже массив заполнен и выведен. Позже рассмотрим ситуацию решения с нуля.

Создание модуля проверки заданий

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

Вот полный текст проверяющего модуля Tasks:

unit Tasks;

uses LightPT;

procedure CheckTaskT(name: string);
begin
  ClearOutputListFromSpaces; 
  case name of
    'ArrTask1': begin

    end;
  end;
end;

initialization
  CheckTask := CheckTaskT;
end.

Здесь ClearOutputListFromSpaces надо писать всегда - оно удаляет пробелы из вывода, которые добавляет при выводе Print, a.Print и ему подобные.

Теперь в ветви 'ArrTask1' оператора case напишем чекер, проверяющий выполнение задания ArrTask1.

Написание чекера

Нужно понимать, что чекер состоит из следующих основных компонент:

  • Проверка типов, введенных пользователем (процедура CheckData)
  • Считывание ввода пользователя в локальные переменные
  • Решение задания в чекере и получение ответа
  • Проверка соответствия ответа тому, который вывел пользователь (процедура CheckOutput)

Рассмотрим каждый из этих пунктов по-отдельности

Проверка типов, введенных пользователем

Проверка типов, введенных пользователем, осуществляется с помощью процедуры CheckData. Простейшая форма вызова CheckData имеет вид:

var n := 10; 
CheckData(Input := |cInt| * n);

или

var n := 10; 
CheckData(|cInt| * n);

Это говорит о том, что мы проверяем, что пользователь ввёл 10 целых чисел (каждое целое число задаётся здесь маркером типа cInt).

Какие ещё варианты задания входных данных могут быть? Их всего пять:

cInt, cRe, cStr, cBool, cChar

Они должны быть объединены в массив типов тем или иным способом, например, с помощью операций сложения массивов и умножения массива на число. Например, выражение

|cInt| * n

задаёт массив из n целых типов, а выражение

|cStr| + |cInt| * n + |cRe| * 2

задаёт массив из одного строкового типа, после которого идёт n целых типов и 2 вещественных.

Если маркер типа умножается на число, его необязательно окаймлять в ||, например:

|cStr| + cInt * n + cRe * 2

Считывание ввода пользователя

Итак, пусть мы написали

var n := 10; 
CheckData(cInt * n);

Это значит, что для решения задания ученик вводит n целых чисел тем или иным способом.

Теперь чтобы проверить задание, мы должны те n значений, которые ввёл ученик, считать во внутренние переменные. Мы это сделаем таким способом:

var a := IntArr(n);

Функция IntArr(n) считывает следующие n данных, которые ввёл пользователь, в массив, рассматривая их как целые. Таким образом, она возвращает массив из n целых.

Какие еще функции можно использовать для считывания ввода пользователя?

Это простейшие:

Int
Re
Boo
Chr
Str

которые считывают один элемент данных и продвигаются вперёд, пары

Int2
Re2

которые возвращают кортежи (пар другого типа нет поскольку это - редкость), а также массивы одного типа из n элементов:

IntArr(n)
ReArr(n)
BooArr(n)
ChrArr(n)
StrArr(n)

Решение задания в чекере и получение ответа

Теперь когда мы считали все данные, которые ввёл пользователь, в переменные, решим задачу так, как должен решать ученик. В данном случае это просто:

var cnt := a.Count(x -> x mod 2 = 0);

Проверка ответа

Теперь необходимо понять, что в заготовке задания мы уже вывели сгенерированный случайный массив и нам лишь надо вывести найденное значение cnt. Для этого воспользуемся процедурой CheckOutput:

CheckOutput(a,cnt);

Всё, текст проверки полностью готов:

procedure CheckTaskT(name: string);
begin
  case name of
    'ArrTask1': begin
      var n := 10;
      CheckData(Input := cInt * n + |cRe|); // проверка типов
      var a := IntArr(n);                   // считывание ввода
      var cnt := a.Count(x -> x mod 2 = 0); // решение задания
      CheckOutput(a,cnt);                   // проверка ответа
    end;
  end;
end;

Как видим, он совсем простой - состоит из 5 строк.

Как это выглядит со стороны ученика

Ученик, решая ArrTask1, вначале просто запустит заготовку и увидит

Далее он попытается решить неправильно:

И наконец

он решит задание правильно.

Добавление тестов (не дописано)

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