Емельянов Эдуард Владимирович (eddy_em) wrote,
Емельянов Эдуард Владимирович
eddy_em

[Latex] Автоматизация подготовки бланковых тестов

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

Однако, TeX — очень мощная система, способная даже решать кубические уравнения! Неужели она не справится с такой простой задачей? Справится, и еще как!

Итак, напишем простенький стилевой файл, позволяющий из текста
\documentclass[a4paper,14pt]{extarticle}
\usepackage[koi8-r]{inputenc} % Классическая кодировка
\usepackage[russian]{babel} % Правила разбиения слов
\usepackage{./TESTS} % наши тесты
\begin{document}
\begin{enumerate}
    \begin{q}{Поняли ли вы, о чем шла речь?}
        \item Да \good
        \item Нет
    \end{q}
    \begin{q}{Сколько ответов может содержать вопрос?}
        \item только 3
        \item только 4
        \item сколько угодно
        \item 2-4 \good
    \end{q}
    \begin{q}{Сколько окружений/команд надо запомнить для составления теста?}
        \item Очень много
        \item 4 (\verb'\q', \verb'\good', \verb'\showtbl' и
                \verb'\trueans')\good
        \item 3
    \end{q}
\end{enumerate}
\newpage
\showtbl
\newpage
\trueans
\end{document}
Получить вот такой тест:

К нему вот такой бланк:

И вот такой эталон:


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

\newcounter{lst}
\renewenvironment{enumerate}%
{\begin{list}{\arabic{lst}.}{\usecounter{lst}\setlength{\itemsep}{0cm}%
\setlength{\parsep}{0cm}\setlength{\topsep}{2mm}}\item[]}{\end{list}}

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

Далее определим окружение q, в которое будем «заворачивать» варианты ответов. Это должен быть нумерованный список, нумерация которого задается русскими буквами. Для него мы тоже будем задавать собственный счетчик.

\newcounter{qwest}
\def\theqwest{\arabic{qwest}}
\newenvironment{q}[1]{\item  #1
\begin{list}{\asbuk{qwest})}{\usecounter{qwest}}}{\end{list}\par}

Окружение мы задаем с одним обязательным параметром — текстом вопроса.

Для того, чтобы отметить правильный вариант ответа, можно воспользоваться стандартным ТеХовским средством вывода в файл, но можно не городить велосипедов, а использовать уже готовый интерфейс — латеховские метки. Так как метка сохраняет значение последнего измененного счетчика, поместив ее после текста правильного ответа, мы автоматически сохраним номер этого ответа. А вот номер вопроса придется сохранять вручную, мы поместим его в текст метки с префиксом «q»:

\newcount\total
\total=0
\def\good{\label{q\arabic{lst}}\global\advance\total by1}

Счетчик total будет содержать общее количество вопросов. Заметьте, что если мы используем больше одной команды good в вопросе, получим ошибку (счетчик total будет превышать общее количество вопросов, текст ошибки будет говорить о неверной ссылке 'q').

Для построения бланков и эталона нам понадобятся дополнительные счетчики:

\newcount\cone
\newcount\ctwo
\newcount\cthree

Кроме того, определим вспомогательные команды. Для того, чтобы выделить из нашей ссылки «q<номер вопроса>» правильный вариант ответа (и чтобы он был значением счетчика), переопределим команду ref:
\def\f@rst#1#2{#1}
\def\ref#1{\expandafter\@setref\csname r@#1\endcsname\f@rst#1}

Бланк и эталон ответов будем рисовать в виде таблицы по 10 вопросов в строку, а т.к. нам необходимо будет еще и заполнять правильные ответы (которые мы оформляем в виде столбцов), в этой таблице каждая ячейка, соответствующая одному вопросу, будет представлять собой вложенную таблицу-столбец. Номера несуществующих вопросов (когда количество вопросов не кратно десяти) заменяем символом X.

Шапка бланка одинакова и для тестового, и для эталонного. Поэтому используем в обоих случаях одну и ту же команду для построения этой шапки:

\def\l@@n@{&}
\def\l@n@{\ifnum\cone<\ctwo\gdef\c@ll{\number\cone}\else\gdef\c@ll{\bf X}\fi
\global\advance\cone by1\global\advance\cthree by1
\l@@n@\hbox to 1cm{\hfil\c@ll\hfil}
\ifnum\cthree<10\l@n@\fi}

Первое определение пришлось сделать, т.к. иначе во второй символ & раскрывался.
Команда l@n@ проверяет, меньше ли номер текущего вопроса (cone) общего количества вопросов (ctwo), если это так, в шапку столбца помещается номер вопроса, иначе — символ X. Номер текущего вопроса увеличивается, так же как счетчик количества вопросов в текущей строке (cthree), если этот счетчик меньше десяти, продолжается рекурсивный вызов этой же команды.

Содержимое таблицы-бланка простое (пустая таблица), поэтому его логично рисовать «в один присест»:

\def\bl@nk{\hbox to 2.5cm{\hfil а)\hfil}&&&&&&&&&&\\\hline
\hbox to 2.5cm{\hfil б)\hfil}&&&&&&&&&&\\\hline
\hbox to 2.5cm{\hfil в)\hfil}&&&&&&&&&&\\\hline
\hbox to 2.5cm{\hfil г)\hfil}&&&&&&&&&&\\\hline\hline}

Здесь просто рисуется таблица с номерами ответов и пустыми ячейками.

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

\def\markX{\hbox to 1cm{\hfil\bf X\hfil}}
\def\@@one{\markX\\\hline\\\hline\\\hline\\}
\def\@@two{\\\hline\markX\\\hline\\\hline\\}
\def\@@three{\\\hline\\\hline\markX\\\hline\\}
\def\@@four{\\\hline\\\hline\\\hline\markX\\}
\def\the@ns#1{\hbox to 1cm{\begin{tabular}{|c}\ifcase#1\null\or\@@one\or
\@@two\or\@@three\or\@@four\fi
\end{tabular}}}

Команду markX мы определили для сокращения писанины, команды @@one…@@four выводят таблицу-столбец, в которой отмечена первая…четвертая строка знаком X. Команда the@ns выдает нужную табличку в зависимости от значения аргумента — номера правильного ответа.

Для заполнения эталонного бланка используем следующие команды:

\def\l@@@{\ifnum\cone<\ctwo\gdef\c@ll{\the@ns{\ref{q\number\cone}}}\else\gdef\c@ll{}\fi
\global\advance\cone by1\global\advance\cthree by1
\l@@n@\c@ll\ifnum\cthree<10\l@@@\fi}
\def\@@tru{\global\advance\cone by-10
\global\cthree=0\hbox to 2.5cm{\begin{tabular}{|c}
\hbox to 2.5cm{\hfil а)\hfil}\\\hline
\hbox to 2.5cm{\hfil б)\hfil}\\\hline
\hbox to 2.5cm{\hfil в)\hfil}\\\hline
\hbox to 2.5cm{\hfil г)\hfil}
\end{tabular}}\l@@@\\\hline\hline}

Вторая команда непосредственно заполняет содержимое таблички на 10 вопросов правильными ответами; первая — рекурсивно подставляет нужные таблички-столбцы. Она работает почти аналогично команде, формирующей шапку с номерами вопросов, только вместо номера вопроса подставляет табличку, в которой отмечен правильный ответ на данный вопрос.


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

\def\@@tmp{\ifnum\cone<\ctwo\global\cthree=0
\l@n@\\\hline\bl@nk\@@tmp\fi}

Заполнение бланка реализуется командой showtbl, которая выводит заголовок таблицы (m@keh@ader), затем — тело таблицы (@@tmp) и, наконец, основание таблицы (m@kefoot@r):
\def\m@keh@ader{\begin{center}\begin{tabular}{||c|c|c|c|c|c|c|c|c|c|c||}
\hline\hline
Вар.~ответа&\multicolumn{10}{|c||}{Номер вопроса}\\\hline}
\def\m@kefoot@r{\end{tabular}\end{center}}
\def\showtbl{{\tabcolsep=0pt\global\ctwo=\total\global\cone=0
\m@keh@ader\@@tmp\m@kefoot@r}}

Ну, а чтобы реализовать построение эталонного бланка, нам необходимо заменить команду bl@nk на @@tru (чтобы вместо пустых ячеек рисовались ячейки с отметками), а затем выполнить то же самое, что делала команда showtbl:
\def\trueans{{\tabcolsep=0pt\global\ctwo=\total\global\cone=0
\m@keh@ader\gdef\bl@nk{\@@tru}\@@tmp\m@kefoot@r}}


Все, наш стилевой файл готов.

Tags: latex
Subscribe

promo eddy_em august 17, 2019 12:33 3
Buy for 10 tokens
Юра намедни напечатал корпус для хронометра. Для первого блина получилось неплохо: И еще немного фотографий:
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic
  • 0 comments