| h1. Система тестирования задач IOI |
| |
| |
| h2. _Version 0.8_ |
| |
| ([История версий|Установка задач IOI - история версий]) |
| |
| h2. {anchor:содержание}Содержание |
| |
| [Введение|#введение] |
| [Как это работает|#howitworks] |
| [Библиотека TesterLib|#testerlib] |
| [Библиотека CheckLib|#checklib] |
| [Шаблон программы Grader|#grader] |
| |
| h2. {anchor:введение}Введение |
| |
| Задачи IOI (во всяком случае, начиная с 2010) по процессу тестирования несколько отличаются от обычно создаваемых на DL (что автоматически означает необходимость выполнять весь процесс от начала до конца самостоятельно). |
| |
| Собственно, процесс выполняется следующим образом: участник присылает _библиотеку_ с решением (или архив с несколькими), которая подключается при компиляции исходника вспомогательной программы. После чего система подсовывает ей по очереди тесты из одной группы и читает результат. После обработки всех тестов группы считается конечный балл (который, само собой, может быть меньше, чем количество тестов в группе). |
| |
| Чтобы устанавливать эти задачи на DL, я разработал более-менее работоспособную замену системы тестирования. Здесь описан процесс установки задачи с её использованием. |
| |
| Библиотеки TesterLib, CheckerLib и код задачи Parrots (IOI 2011 d2 t3) можно скачать [одним архивом|http://dl.gsu.by/images/agulenko/IOI_tester.rar]. |
| |
| h2. {anchor:howitworks}Как это работает |
| |
| Итак, что происходит при запуске тестирования? |
| * Delta открывает файл {{[task.cfg|http://dl.gsu.by/doc/use/taskcfg.htm]}} и видит там текст следующего содержания: |
| {code:title=task.cfg} |
| TYPE = USERS |
| CHECKSUBJECT = FILE |
| CHECKFILES = {*.*} |
| CHECKER = 'tester.exe' |
| {code} |
| |
| * В соответствии с текстом, Delta копирует все файлы в корне задачи в корень папки тестирования и запускает {{tester.exe}}, ожидая, что тот закончит работу не позднее чем через минуту |
| * Тестер производит тестирование |
| * Delta читает файл результата и выводит результат как за один тест. |
| |
| Тестер выполняет задачи, которые по-хорошему должна была выполнять сама Delta: |
| * Первым делом он читает свой файл конфигурации |
| * Далее он запускает скрипт подготовки, выполняющий компиляцию решения и т.п. |
| * Переименовывая по очереди входные файлы тестов в нужное название, тестер запускает программу {{checker.exe}}, которая отвечает за тестирование одного теста и выдачу результата этого тестирования. |
| * Собрав данные по всем тестам группы, тестер вычисляет балл за группу тестов (если какой-то тест в группе был не пройден, последующие уже не проверяются) |
| * В конце тестер выводит информацию по тестам в файл результата |
| |
| Скрипт подготовки обычно называется {{[prepare.bat|http://dl.gsu.by/images/agulenko/IOI11_Parrots/prepare.bat]}} и выполняет следующие действия: |
| * Даёт файлам исходников правильные названия (они переименованы в малополезной попытке получения хоть какой-то защиты от обмана) |
| | * Запускает один из скриптов компиляции Дельты |
| | * Запускает один из скриптов компиляции |
| *- Для получения информации об ошибках компиляции необходимо проверять/возвращать ErrorLevel (см. скрипт в примере). |
| |
| Чекер выполняет следующие действия: |
| * Запускает скрипт {{[limiter.bat|http://dl.gsu.by/images/agulenko/IOI_common/limiter.bat]}} (передающий работу лимитеру Дельты) с полученными ограничениями по времени/памяти для программы-оценщика ({{grader.exe}}) |
| * Если программа завершилась некорректно или была остановлена лимитером (или ничего не вывела), чекер замещает её файл вывода своим (где "всё плохо") |
| |
| Оценщик компилируется в начале тестирования (решение - одна из его библиотек). Он: |
| * Считывает входной файл и готовит переменные окружения |
| * Запускает функцию (или функции) решения, как описано в условии |
| * Контролирует результаты выполнения функции, проверяет результаты её выполнения (насколько это позволяют данные входного файла) |
| |
| h2. {anchor:testerlib}Библиотека TesterLib (Free Pascal) |
| |
| Для написания тестера достаточно написать несколько специфических (для задачи) функций/процедур (если они нужны), всё остальное реализовано в библиотеке {{[TesterLib|http://dl.gsu.by/images/agulenko/IOI_common/TesterLib.pp]}}. |
| |
| h3. {anchor:testerlib_cfg}Конфигурация |
| |
| Тестер при запуске считывает файл {{tester.cfg}}, который обрабатывается следующим образом: |
| | * Строка начиная с первого символа "#" и до конца игнорируется (комментарии) |
| | * Строка начиная с первого символа "{{#}}" и до конца игнорируется (комментарии) |
| * Пустые строки (содержащие только пробелы и/или комментарий) игнорируются |
| | * Если строка начинается с символа "<", начинается режим чтения групп тестов (после символа может быть указано их количество) |
| | * Если строка начинается с символа "{{<}}", начинается режим чтения групп тестов (после символа может быть указано их количество) |
| * В противном случае строка обрабатывается как опция (в формате "название=значение") |
| |
| В режиме чтения групп тестов (игнорирование по тем же правилам): |
| | * Если строка начинается с символа ">", режим чтения групп заканчивается. |
| * Строка разбивается на части, разделяемые запятой (",") |
| | * Если строка начинается с символа "{{>}}", режим чтения групп заканчивается. |
| * Строка разбивается на части, разделяемые запятой ("{{,}}") |
| * Первая часть - число баллов за тест |
| * Вторая (если есть и не пуста) - число тестов в группе (иначе 0) |
| | * Третья - дополнительная пометка (для вычисления количества баллов; тж. см. FRate в подразделе "Библиотека") |
| | * Третья - дополнительная пометка (для вычисления количества баллов; тж. см. [Rate()|#testerlib_rate] в параметрах [Body()|#testerlib_body]) |
| |
| Поддерживаются следующие опции: |
| | * InFile - входной файл (по умолчанию "grader.in") |
| * OutFile - выходной файл (по умолчанию "grader.out") |
| * ResFile - файл результата (по умолчанию "$result$.txt") |
| * PreRun - скрипт подготовки (указывать всегда, т.к. по умолчанию не выполняется) |
| * MemLimit - ограничение по памяти (можно указывать в байтах или добавлять "B", "KB", "MB", "GB"; по умолчанию "256MB") |
| * TimeLimit - ограничение по времени (в секундах; по умолчанию "2") |
| * Limiter - скрипт вызова лимитера (по умолчанию "limiter.bat") |
| | * InFile - входной файл (по умолчанию "{{grader.in}}") |
| * OutFile - выходной файл (по умолчанию "{{grader.out}}") |
| * ResFile - файл результата (по умолчанию "{{$result$.txt}}") |
| * PreRun - скрипт подготовки (указывать всегда, т.к. по умолчанию не выполняется; если выполнится с ненулевым кодом завершения, это будет обработано как ошибка компиляции решения) |
| * MemLimit - ограничение по памяти (можно указывать в байтах или добавлять "{{B}}", "{{KB}}", "{{MB}}", "{{GB}}"; по умолчанию "{{256MB}}") |
| * TimeLimit - ограничение по времени (можно указывать в милисекундах или добавлять "{{MS}}", "{{S}}"; по умолчанию "{{2s}}") |
| * Limiter - скрипт вызова лимитера (по умолчанию "{{limiter.bat}}") |
| * Comment - выводимые комментарии ("{{+}}" для прошедших тестов, "{{\-}}" для непрошедших, "{{\*}}" для частично прошедших; по умолчанию "{{\-\*}}") |
| |
| h3. {anchor:testerlib_unit}Библиотека |
| |
| Для использования тестера нужно подключить библиотеку в начале файла: |
| {code:title=tester.pas} |
| Uses |
| TesterLib; |
| // ... |
| {code} |
| В теле тестера нужно вызвать две процедуры: |
| {code:title=tester.pas} |
| // ... |
| BEGIN |
| Body(); |
| POut(); |
| END. |
| {code} |
| Эти процедуры имеют несколько параметров-указателей со значениями по умолчанию (все {{NIL}}). Для передачи указателя нужно указать имя переменной (или процедуры/функции) с собакой перед ним: {{@Data}}. |
| [Пример тестера.|http://dl.gsu.by/images/agulenko/IOI11_Parrots/tester.pas] |
| |
| h3. {anchor:testerlib_body}Body() |
| |
| {code:title=tester.pas} |
| Body (@Data, @Check, @Rate, @Reset, @Print); |
| {code} |
| * {{[Data|#testerlib_data] = Pointer}} |
| Структура с данными (любая) для хранения данных по группе тестов |
| | * {{[Check|#testerlib_check] = Function (Data : Pointer; Chk : PText): Boolean;}} |
| Функция проверки теста; по умолчанию сравнивает первую строку со строковой константой {{Approval}}. |
| * {{[Rate|#testerlib_rate] = Function (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Data : Pointer): LongWord;}} |
| | * {{[Check|#testerlib_check] = Function (Data : Pointer; Var Comment : AnsiString; Chk : PText): Boolean;}} |
| Функция проверки теста; по умолчанию сравнивает первую строку со строковой константой {{Approval}} и сохраняет её значение как комментарий. |
| * {{[Rate|#testerlib_rate] = Function (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Var Comment : AnsiString; Data : Pointer): LongWord;}} |
| Функция оценки группы тестов; по умолчанию ставит Max за пройденную группу и 0 за непройденную. |
| | * {{[Reset|#testerlib_reset] = Procedure (Tests : LongWord; Remark : AnsiString; Data : Pointer);}} |
| | * {{[Reset|#testerlib_reset] = Procedure (Tests : LongWord; Remark : AnsiString; Data : Pointer);}} |
| Процедура инициализации {{Data}} перед новой группой тестов; по умолчанию заполняет нулями по размеру (*НЕ* использовать с полями {{AnsiString}}\!). |
| | * {{[Print|#testerlib_print] = Procedure (Data : Pointer)}} |
| | * {{[Print|#testerlib_print] = Procedure (Data : Pointer; Remark : AnsiString)}} |
| Делает отладочный вывод; по умолчанию -- ничего. |
| |
| В {{Body()}} происходит следующее: |
| * Перебираются тесты (именованные в формате DL, т.е. "$\{номер\}.in" и "$\{номер\}.out") и группы тестов (группа 1 содержит первые {{Tests\[1\]}} тестов, группа 2 содержит следующие {{Tests\[2\]}} тестов, и т.д.). Здесь и далее: {{i}} -- номер группы, {{j}} -- номер теста в группе, {{k}} -- глобальный номер теста. |
| * В начале обработки группы вызывается процедура {{Reset(Tests\[i\], Remark\[i\], Data)}}, которая инициализирует {{Data\^}} для обработки новой группы (по умолчанию выполняется {{FillChar(Data^, SizeOf(Data^), $00)}}). |
| * {{Failed\[i\]}} (пометка о проваленном тесте) устанавливается в {{False}}. |
| * Отладочный вывод: в stdout выводится {{(i + "\[" + Tests\[i\] + "\]")}} |
| * В начале обработки теста выводится "+" |
| * Если установлен {{Failed\[i\]}}, тест пропускается |
| * Файл {{k}} + {{".in"}} переименовывается в {{InFile}} |
| * Вызывается чекер с параметрами {{Limiter}}, {{MemLimit}} и {{TimeLimit}} |
| * {{InFile}} возвращается на место |
| * Если файл {{k}} + {{".out"}} существует, он открывается для чтения в {{Chk}} и адрес сохраняется в {{PChk}}, иначе в {{PChk}} сохраняется {{NIL}} |
| * Открывается {{OutFile}} и вызывается {{Check(Data, PChk)}}, которая читает из {{Input}} выходной файл (а из {{PChk\^}} проверочный, если там не {{NIL}}), обновляет {{Data\^}} и возвращает новое значение {{Failed\[i\]}}. |
| * Файлы закрываются |
| * После обработки всех тестов в группе балл за группу ({{Res\[i\]}}) вычисляется с помощью {{Rate(Max\[i\], Failed\[i\], Remark\[i\], Data)}}; также она может обновить {{Failed\[i\]}} на случай, если группа имеет специфическую проверку, не реализованную в {{Check()}} (по умолчанию {{Rate()}} просто проверяет текущее значение {{Failed\[i\]}}). |
| | * Отладочный вывод: выполняется {{Print(Data)}}, после чего выводится {{(" " + Res\[i\])}}. |
| | * Отладочный вывод: выполняется {{Print(Data)}}, после чего выводится {{(" " + Res\[i\])}} и (для соответствующих групп тестов, см. [Конфигурация|#testerlib_cfg]) комментарий. |
| * {{Res\[i\]}} прибавляется к суммарному баллу. |
| |
| h3. {anchor:testerlib_pout}POut() |
| |
| {code:title=tester.pas} |
| Procedure POut (@IsPartial); |
| {code} |
| * {{[IsPartial|#testerlib_ispartial] = Function (Remark : AnsiString): Boolean;}} |
| Проверяет тест на наличие частичного балла (для вывода в виде Ball/Max); по умолчанию возвращает {{False}}. |
| |
| В {{POut()}} происходит следующее: |
| * Открывается {{ResFile}}, в первую строку выводится суммарный балл |
| * Во вторую выводится информация по группам через запятую: |
| * {{"-"}} для проваленных ({{Failed\[i\]}}) |
| * {{"+"}} для полностью пройденных тестов |
| * {{"*"}} для частично пройденных тестов |
| * Также, для пройденных тестов без частичной разбалловки в скобках выводится балл; для непроваленных тестов с частичным баллом - "балл/максимум". Считается ли тест частичным, определяет функция {{IsPartial(Remark\[i\])}} (вариант по умолчанию всегда возвращает {{False}}). |
| | * Для соответствующих групп тестов (см. [Конфигурация|#testerlib_cfg]) выводится комментарий. |
| |
| h3. {anchor:testerlib_data}Data |
| |
| {code:title=tester.pas} |
| Data : Pointer; |
| {code} |
| {{Data}} -- указатель на структуру данных, сохраняющих данные по тестам (с помощью которых производится вычисление частичных баллов). Он может ссылаться на переменную любого типа. Удобнее всего делать его записью ({{Record}} -- группа переменных): |
| {code:title=tester.pas} |
| TYPE |
| TData = Record |
| Sum, R : LongWord; |
| Q : Extended; |
| End; |
| |
| VAR |
| Data : TData; |
| {code} |
| При работе с полями параметра Data удобно работать в блоке {{with}}: |
| {code:title=tester.pas} |
| Procedure ... |
| Begin |
| With (TData(Data^)) Do Begin |
| Inc(R); |
| Q:=Sum / R; |
| End; |
| End; |
| {code} |
| Если вычислять частичные баллы не требуется, обычно можно обойтись без сохранения промежуточных данных (исключение -- если группа тестов имеет дополнительное ограничение, которое не определяется grader'ом). В этом случае можно вызвать {{Body()}} с параметрами по умолчанию, или передать первым параметром {{NIL}} (если нужно передать ссылку на функцию/процедуру). |
| |
| | h3. {anchor:testerlib_check}Check |
| | h3. {anchor:testerlib_check}Check() |
| |
| {code:title=tester.pas} |
| | Function Check (Data : Pointer; Chk : PText): Boolean; |
| | Function Check (Data : Pointer; Var Comment : AnsiString; Chk : PText): Boolean; |
| {code} |
| | Функция {{Check}} получает в качестве параметров два указателя: {{Data}} (переданный первым параметром в {{Body}}) и {{Chk}} (указатель на открытый для чтения файл проверочных данных теста -- \{номер_теста\}.out; если этого файла нет, указатель содержит {{NIL}}); в {{Input}} открыт выходной файл grader'а. После прочтения данных из этих файлов и сохранения нужной информации в {{Data\^}}, функция возвращает {{True}} (если тест был пройден) или {{False}} (в противном случае). |
| | Функция {{Check}} получает в качестве параметров два указателя: {{Data}} (переданный первым параметром в {{Body}}) и {{Chk}} (указатель на открытый для чтения файл проверочных данных теста -- \{номер_теста\}.out; если этого файла нет, указатель содержит {{NIL}}); в {{Input}} открыт выходной файл grader'а. После прочтения данных из этих файлов и сохранения нужной информации в {{Data\^}}, функция возвращает {{True}} (если тест был пройден) или {{False}} (в противном случае). Также она может модифицировать комментарий группы теста ({{Comment}}) на своё усмотрение. |
| |
| Первая строка выходного файла пройденного теста содержит строку, равную константе {{Approval}}. Последующие строки содержат данные, заносимые в {{Data\^}} (например, количество запросов -- в {{Data\^}} сохраняется максимум как худшее значение). |
| Также в этой функции выполняются проверки, для которых недостаточно знать входные данные (эти проверки выполняет grader). Нужные данные получаются из файла {{Chk\^}}. |
| |
| | h3. {anchor:testerlib_rate}Rate |
| | h3. {anchor:testerlib_rate}Rate() |
| |
| {code:title=tester.pas} |
| | Function Rate (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Data : Pointer): LongWord; |
| | Function Rate (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Var Comment : AnsiString; Data : Pointer): LongWord; |
| {code} |
| | Функция {{Rate}} получает в качестве параметров {{Max}} (максимальный балл за группу тестов), {{Failed}} (содержит {{True}} если все тесты в группе были пройдены), {{Remark}} (пометка -- третья часть описания группы тестов) и указатель {{Data}}. Она возвращает итоговый балл за группу; если группа по какой-то причине не пройдена, значение {{Failed}} устанавливается в {{False}}. |
| | Функция {{Rate}} получает в качестве параметров {{Max}} (максимальный балл за группу тестов), {{Failed}} (содержит {{True}} если все тесты в группе были пройдены), {{Remark}} (пометка -- третья часть описания группы тестов) и указатель {{Data}}. Она возвращает итоговый балл за группу; если группа по какой-то причине не пройдена, значение {{Failed}} устанавливается в {{False}}. Также можно изменить значение комментария ({{Comment}}). |
| |
| Обычно функция возвращает {{Max}} (если {{Failed = True}}) или {{0}} (в противном случае). Необходимость другого поведения (дополнительная проверка или частичный балл) определяется пометкой, а для его обработки используется {{Data\^}}. Если группа имеет одно или несколько дополнительных ограничений, их можно описать в пометке как числа (через пробел), а для прочтения использовать процедуру {{ReadStr}} (она работает так же, как и {{Read}}, но первым параметром принимает строку, из которой читаются данные); в простых случаях достаточно помещать в пометку один символ. |
| |
| | h3. {anchor:testerlib_reset}Reset |
| | h3. {anchor:testerlib_reset}Reset() |
| |
| {code:title=tester.pas} |
| Procedure Reset (Tests : LongWord; Remark : AnsiString; Data : Pointer); |
| {code} |
| Процедура {{Reset}} получает в качестве параметров {{Tests}} (количество тестов в группе), {{Remark}} (пометка) и указатель {{Data}}. Она инициализирует структуру {{Date\^}} для работы с новой группой тестов. |
| |
| Обычно процедура заполняет {{Data\^}} нулевым байтом (если {{Data}} -- не {{NIL}}). Однако это не всегда хорошая идея (скажем, если {{Data\^}} содержит поля типа {{AnsiString}} или другие указатели). Также, если для работы с группой требуется заранее иметь информацию о ней, можно использовать {{Tests}} и {{Remark}}. |
| |
| | h3. {anchor:testerlib_print}Print |
| | h3. {anchor:testerlib_print}Print() |
| |
| {code:title=tester.pas} |
| | Procedure Print (Data : Pointer); |
| | Procedure Print (Data : Pointer; Remark : AnsiString); |
| {code} |
| | Процедура {{Print}} получает в качестве параметра указатель {{Data}}. Она делает отладочный вывод в консоль (эта информация может быть полезна при запуске тестера вручную). Вывод выполняется после вычисления баллов за группу. В качестве источника данных используется структура, на которую указывает {{Data}}. |
| | Процедура {{Print}} получает в качестве параметра указатель {{Data}} и пометку группы тестов ({{Remark}}). Она делает отладочный вывод в консоль (эта информация может быть полезна при запуске тестера вручную). Вывод выполняется после вычисления баллов за группу. В качестве источника данных используется структура, на которую указывает {{Data}}. |
| |
| Обычно {{Print}} ничего не делает. |
| |
| | h3. {anchor:testerlib_ispartial}IsPartial |
| | h3. {anchor:testerlib_ispartial}IsPartial() |
| |
| {code:title=tester.pas} |
| Function IsPartial (Remark : AnsiString): Boolean; |
| {code} |
| Функция {{IsPartial}} получает в качестве параметра {{Remark}} (пометку). Она определяет, может ли группа получить частичный балл (чтобы полностью пройденные группы тестов с частичными баллами отображались в формате Ball/Max). |
| |
| Обычно {{IsPartial}} возвращает {{False}}. |
| |
| h2. {anchor:checklib}Библиотека CheckLib |
| |
| Для написания чекера достаточно использовать библиотеку {{[CheckLib|http://dl.gsu.by/images/agulenko/IOI_common/CheckLib.pp]}} и написать несколько строк кода. |
| |
| Чекер получает 3 параметра: название скрипта-лимитера и его параметры-ограничения (по памяти и по времени). Обработка этих параметров (как большая часть остального функционала) выполняется библиотекой. Всё, что нужно сделать в теле -- вызвать функцию запуска для каждого grader'а и переместить промежуточные файлы. |
| |
| Если grader называется {{gradera}}, он будет читать данные из файла {{gradera.in}} и выводить данные в {{gradera.out}}. Соответственно, если после этого запускается grader, который называется {{graderb}}, нужно переместить {{gradera.out}} в {{graderb.in}}. В файле {{tester.cfg}} входной файл будет называться {{gradera.in}}, а выходной -- {{graderb.out}}. Для удобства, используется константа {{Grader}}. |
| |
| Таким образом, тело этого чекера будет выглядеть следующим образом: |
| {code:title=checker.pas} |
| Uses |
| CheckLib, SysUtils; |
| |
| CONST |
| Gradera = Grader+'a'; |
| Graderb = Grader+'b'; |
| |
| BEGIN |
| Run(Gradera, Graderb+'.out'); |
| DeleteFile(Graderb+'.in'); |
| RenameFile(Gradera+'.out', Graderb+'.in'); |
| Run(Graderb, Graderb+'.out'); |
| END. |
| {code} |
| | Процедура {{Run}} получает в качестве параметров две строки: название grader'а и название (последнего) выходного файла. Она запускает grader с помощью лимитера, проверяет вывод и, если grader не создал выходной файл (или если лимитер прервал его работу), создаёт выходной файл с соответствующей пометкой в первой строке и прерывает работу чекера. |
| | Процедура {{Run()}} получает в качестве параметров две строки: название grader'а и название (последнего) выходного файла. Она запускает grader с помощью лимитера, проверяет вывод и, если grader не создал выходной файл (или если лимитер прервал его работу), создаёт выходной файл с соответствующей пометкой в первой строке и прерывает работу чекера. |
| |
| h2. {anchor:grader}Шаблон программы Grader |
| |
| Как было упомянуто выше, grader с названием {{gradera}} читает данные из файла {{gradera.in}} и выводит результат в {{gradera.out}}. Первая строка вывода содержит {{Approval}} ('OK') для пройденного теста (или любую другую строку для непройденного), последующие -- информацию для вычисления частичного балла (или пост-проверки). |
| |
| Обычно чтение выносится в процедуру {{ReadAll}}, вывод -- в процедуру {{WriteAll}}. В теле программы между их вызовами выполняется инициализация вспомогательной библиотеки (если таковая имеется), вызовы функций/процедур решения и проверка их работы. |
| |
| Таким образом, тело программы может выглядеть приблизительно так: |
| {code:title=gradera.pas} |
| Uses |
| // Библиотека решения, остальные библиотеки |
| |
| CONST |
| InFile = 'gradera.in'; |
| OutFile = 'gradera.out'; |
| |
| CONST |
| Approval = 'OK'; |
| |
| VAR |
| // Объявление глобальных переменных |
| |
| Procedure ReadAll(); |
| Var |
| // Объявление локальных переменных |
| Begin |
| Assign(Input, InFile); |
| Reset(Input); |
| // Чтение входных данных |
| Close(Input); |
| End; |
| |
| Procedure WriteAll(); |
| Var |
| // Объявление локальных переменных |
| Begin |
| Assign(Output, OutFile); |
| Reset(Output); |
| // Вывод результатов |
| Close(Output); |
| End; |
| |
| // Вспомогательные процедуры и функции |
| |
| VAR |
| // Объявление локальных для тела переменных |
| BEGIN |
| ReadAll(); |
| // Вызов решения |
| // Проверка результата |
| WriteAll(); |
| END. |
| {code} |