I defined here some commands which allow to assign categories to questions in an exam class document, based on the code from the exam
class itself. I thought it would be clearer to put most of the code in a category.sty
file, which is at the end of the answer. I ran some tests and I think it should work in most situations. I noticed however that it will probably not work with the hyperref
package.
The new commands defined are the following.
\newcategory{<name>}{<label>}
. This command allows to define a new category in the exam. <name>
is the name of the category as it will be displayed in text, and <label>
is the label with which the category will be accessed in the code. This command should be placed in the preamble.
\category{<label>}
. Returns the name of the category associated to the label <label>
, all in lowercase characters.
\Category{<label>}
. Same as \category
, but capitalizes the first letter of the category name.
\categorynumber{<label>}
. Returns the number of the category associated to the label <label>
. Categories are numbered in the order in which they are defined from the \newcategory
command.
\categorize{<label>}
. To be used in a \question
item. Associates the corresponding question to the category labelled <label>
. The \categorize
command can take only a single label as an argument, but multiple \categorize
commands can be used with the same question to associate a question to multiple categories.
\categorytable
. Creates a table displaying the category names with the corresponding numbers. The category table does not need to be called for the categories to be numbered.
If categories are defined and questions are categorized, the categories will be displayed in horizontal complete grading tables (grade, point, bonus, or combined) if they are indexed by questions. In a partial or a vertical grading table, or in a table indexed by pages, categories will simply be ignored.
Here is a small example to show the use of most commands.
\documentclass[addpoints]{exam}
\usepackage{wrapfig}
\usepackage{category}
\newcategory{Analysis}{cat:an}
\newcategory{Topology}{cat:topo}
\newcategory{Algebra}{cat:alg}
\begin{document}
\begin{wraptable}{r}{0.2\textwidth}
\centering
\categorytable
\caption{Example of \textbackslash\texttt{categorytable}.}
\end{wraptable}
The categories used in this exam are \category{cat:an} (\categorynumber{cat:an}), \category{cat:topo} (\categorynumber{cat:topo}) and \category{cat:alg} (\categorynumber{cat:alg}).
\medskip\combinedgradetable[h]
\begin{questions}
\question[15] \categorize{cat:an} First question.
\question[25] \categorize{cat:an}\categorize{cat:topo} Second question.
\question[30] \categorize{cat:topo} Third question.
\question[30] \categorize{cat:alg} Fourth question.
\bonusquestion[5] \categorize{cat:topo}\categorize{cat:alg} Bonus question.
\bonusquestion[5] Bonus question, not categorized.
\end{questions}
\end{document}
By default, the word "Category:" will be displayed at the beginning of the corresponding row in a grade table, but the commands \hcword{newname}
, \bhcword{newname}
and \chcword{newname}
could be used to replace "Category:" by "newname" in the associated table (grade, bonus and combined, respectively). The similar command \cwordctbl
allows to change the name displayed in a \categorytable
, which is "Categories" by default. If a question is left uncategorized, what is displayed in the category row for this question is defined by the \nocword
command. The default is \nocword{--}
, but a similar call will replace --
by the argument.
The following is the category.sty
file. If you prefer to put that code in your preamble, just load the refcount
package, and put everything but the first three lines of the following code between \makeatletter
and \makeatother
.
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{category}
\RequirePackage{refcount}
\def\hcword#1{\def\@hcword{#1}}
\def\bhcword#1{\def\@bhcword{#1}}
\def\chcword#1{\def\@chcword{#1}}
\def\cwordctbl#1{\def\@cwordctbl{#1}}
\def\nocword#1{\def\@nocword{#1}}
\hcword{Category:}
\bhcword{Category:}
\chcword{Category:}
\cwordctbl{Categories}
\nocword{--}
\newcounter{num@cats}
\newcounter{current@category}
\newcounter{current@catofq}
\newcounter{qfor@lastc}
\def\relaxwitharg#1{\relax}
\def\newcategory#1#2{%
\stepcounter{num@cats}
\expandafter\gdef\csname category\romannumeral\thenum@cats \endcsname{#1}%
\expandafter\edef\csname numberof#2 \endcsname{\thenum@cats}%
\protected@write\@auxout{}%
{\string\newlabel{#2}{{\thenum@cats}{\thepage}}}%
}% newcategory
\def\categorynumber#1{%
\csname numberof#1 \endcsname%
}% categorynumber
\def\category#1{%
\expandafter\MakeLowercase \csname category\romannumeral\categorynumber{#1} \endcsname%
}% category
\def\Category#1{%
\edef\temp{\csname category\romannumeral\categorynumber{#1}\endcsname}%
\expandafter\MakeUppercase{\expandafter\@car\temp\@nil}%
\expandafter\@cdr\temp\@nil%
}% Category
\def\categorize#1{%
\@ifundefined{c@num@catsofq@\romannumeral\thequestion}{%
\newcounter{num@catsofq@\romannumeral\thequestion}%
}{}%
\stepcounter{num@catsofq@\romannumeral\thequestion}%
\immediate\write\@mainaux%
{\string\gdef\string\category\romannumeral\the\value{num@catsofq@\romannumeral\thequestion}ofq@\romannumeral\thequestion{\string\ref{#1}}}%
}% categorize
\def\do@catloop@ctbl{%
\stepcounter{current@category}%
\thecurrent@category%
\hidden@ampersand%
\csname category\romannumeral\thecurrent@category \endcsname%
\ifnum \value{current@category} = \value{num@cats}\relax%
\\
\hline
\end{tabular}
\let\nextdo@catloop@ctbl\relax
\else
\\
\let\nextdo@catloop@ctbl\do@catloop@ctbl
\fi
\nextdo@catloop@ctbl
}% do@catloop@ctbl
\def\categorytable{%
\begin{tabular}{ll}
\multicolumn{2}{c}{\@cwordctbl} \\
\hline
\do@catloop@ctbl
}% categorytable
\def\do@lastcloop{%
\stepcounter{qfor@lastc}%
\@ifundefined{exam@numquestions}{%
\let\nextdo@lastcloop=\relax
}{%
\ifnum \value{qfor@lastc} > \exam@numquestions\relax
\let\nextdo@lastcloop=\relax%
\else
\@ifundefined{thenum@catsofq@\romannumeral\theqfor@lastc}%
{%
\immediate\write\@mainaux%
{\string\xdef\string\exam@catsofquestion\romannumeral\theqfor@lastc{0}}%
\let\nextdo@lastcloop=\do@lastcloop%
}{%
\immediate\write\@mainaux%
{\string\xdef\string\exam@catsofquestion\romannumeral\theqfor@lastc{\csname thenum@catsofq@\romannumeral\theqfor@lastc \endcsname}}%
\let\nextdo@lastcloop=\do@lastcloop%
}%
\fi
}%
\nextdo@lastcloop
}% do@lastcloop
\AtEndDocument{%
\do@lastcloop%
}
\def\do@catsofqloop#1{%
\stepcounter{current@catofq}%
\@ifundefined{exam@catsofquestion\romannumeral #1}{%
\let\nextdo@catsofqloop=\relaxwitharg
}{%
\ifnum \value{current@catofq} = \csname exam@catsofquestion\romannumeral #1 \endcsname\relax
\csname category\romannumeral\thecurrent@catofq ofq@\romannumeral #1 \endcsname%
\let\nextdo@catsofqloop=\relaxwitharg
\else
\csname category\romannumeral\thecurrent@catofq ofq@\romannumeral #1 \endcsname,\,%
\let\nextdo@catsofqloop=\do@catsofqloop
\fi
}%
\nextdo@catsofqloop{#1}%
}% do@catsofqloop
\def\categoriesofquestion#1{\@ifundefined{categoryiofq@\romannumeral #1}%
{\mbox{\@nocword}}%
{%
\setcounter{current@catofq}{0}%
\do@catsofqloop{#1}
}%
}% categoriesofquestion
\def\categoriesof@index#1{%
\categoriesofquestion{\arabic{#1}}%
}% categoryof@index
\def\do@catloop@h{%
\increment@index{pq@index}%
\ifnum \value{pq@index} > \last@pq@index\relax
% Do nothing!
\else
\hidden@ampersand
\addtocounter{cols@done}{1}%
\categoriesof@index{pq@index}%
\fi
\ifnum \value{pq@index} < \last@pq@index\relax
\ifnum \value{cols@done} < \value{num@cols}\relax
\let\nextdo@catloop@h=\do@catloop@h
\else
\let\nextdo@catloop@h=\relax
\fi
\else
\let\nextdo@catloop@h=\relax
\fi
\nextdo@catloop@h
}% do@catloop@h
\def\do@lines@h{%
\addtocounter{current@row}{1}%
\iftbl@pgs
\if@bonus
\@bhpgword
\else
\@hpgword
\fi
\else
\if@bonus
\@bhqword
\else
\@hqword
\fi
\fi
\setcounter{cols@done}{0}%
\do@pq@indexloop@h
\ifnum \value{cols@done} < \value{num@cols}\relax
\ifnum \value{current@row} = \value{num@rows}\relax
\do@htword@h
\else
\setcounter{@iterator}{\value{num@cols}}%
\addtocounter{@iterator}{-\value{cols@done}}%
\do@emptycols@h
\fi
\fi
\\
\hline
\iftbl@pgs
%Do nothing
\else
\if@partial
%Do nothing
\else
\setcounter{pq@index}{0}%
\setcounter{cols@done}{0}%
\@hcword
\do@catloop@h
\hidden@ampersand
\\
\hline
\fi
\fi
% Point values go here!
\setcounter{cols@done}{0}%
\if@bonus
\@bhpword
\do@bptloop@h
\else
\@hpword
\do@ptloop@h
\fi
\ifnum \value{cols@done} < \value{num@cols}\relax
\ifnum \value{current@row} = \value{num@rows}\relax
\if@bonus
\do@totalbpts@h
\else
\do@totalpts@h
\fi
\else
\setcounter{@iterator}{\value{num@cols}}%
\addtocounter{@iterator}{-\value{cols@done}}%
\do@emptycols@h
\fi
\fi
% Scores?
\if@scores
\\
\hline
\if@bonus
\@bhsword \hidden@ampersand
\else
\@hsword \hidden@ampersand
\fi
\setcounter{cols@done}{0}%
\do@sloop@h
\fi
\ifnum \value{current@row} = \value{num@rows}\relax
\\
\hline
\end{tabular}%
\else
\\
\hline\hline
\fi
\ifnum \value{current@row} < \value{num@rows}\relax
\let\nextdo@lines@h=\do@lines@h
\else
\let\nextdo@lines@h=\relax
\fi
\nextdo@lines@h
} %do@lines@h
\def\do@comblines@h{%
\addtocounter{current@row}{1}%
\iftbl@pgs
\@chpgword
\else
\@chqword
\fi
\setcounter{cols@done}{0}%
\do@pq@indexloop@h
\ifnum \value{cols@done} < \value{num@cols}\relax
\ifnum \value{current@row} = \value{num@rows}\relax
\do@htword@h
\else
\setcounter{@iterator}{\value{num@cols}}%
\addtocounter{@iterator}{-\value{cols@done}}%
\do@emptycols@h
\fi
\fi
\\
\hline
\iftbl@pgs
%Do nothing
\else
\if@partial
%Do nothing
\else
\setcounter{pq@index}{0}%
\setcounter{cols@done}{0}%
\@chcword
\do@catloop@h
\hidden@ampersand
\\
\hline
\fi
\fi
% Point values go here!
\@chpword
\setcounter{cols@done}{0}%
\do@ptloop@h
\ifnum \value{cols@done} < \value{num@cols}\relax
\ifnum \value{current@row} = \value{num@rows}\relax
\do@totalpts@h
\else
\setcounter{@iterator}{\value{num@cols}}%
\addtocounter{@iterator}{-\value{cols@done}}%
\do@emptycols@h
\fi
\fi
\\
\hline
% Bonus point values go here!
\@chbpword
\setcounter{cols@done}{0}%
\do@bptloop@h
\ifnum \value{cols@done} < \value{num@cols}\relax
\ifnum \value{current@row} = \value{num@rows}\relax
\do@totalbpts@h
\else
\setcounter{@iterator}{\value{num@cols}}%
\addtocounter{@iterator}{-\value{cols@done}}%
\do@emptycols@h
\fi
\fi
% Scores?
\if@scores
\\
\hline
\@chsword \hidden@ampersand
\setcounter{cols@done}{0}%
\do@sloop@h
\fi
\ifnum \value{current@row} = \value{num@rows}\relax
\\
\hline
\end{tabular}%
\else
\\
\hline\hline
\fi
\ifnum \value{current@row} < \value{num@rows}\relax
\let\nextdo@comblines@h=\do@comblines@h
\else
\let\nextdo@comblines@h=\relax
\fi
\nextdo@comblines@h
}% do@comblines@h
documentclass
and finishing withend document