9.3 ⁑The probsoln Package
The probsoln package [94] provides a means to define problems with their associated solution. These definitions may be placed in an external .tex file. You can then load all problems or a subset of problems (possibly randomly selected) into a dataset. You can have more than one dataset, each of which could, for example, represent a topic. In your document you can iterate through these datasets and display the problem, the solution or both. This means that you can gather the solutions together in another part of the document. Since probsoln is a package, you need to find an appropriate document class.
The probsoln package has the following options:
answers
- Show the solutions.
noanswers
- Hide the solutions (default).
draft
- Display the problem label and dataset name when a problem is used.
final
- Don't display the problem label and dataset name (default).
usedefaultargs
- Make
\thisproblem
use the default arguments supplied with the problem definition. nousedefaultargs
- Make
\thisproblem
prompt for arguments (default).
\documentclass
are also
passed to packages, so if you use the draft
class option,
it will automatically enable probsoln's draft
option,
unless you have explicitly used probsoln's final
option.
At the time of writing the current version of probsoln is
version 3.04 (2012-08-23).
Some of the features described here aren't available for earlier
versions. Since defining the problems and their solutions requires
either a command or an environment that gathers its contents,
verbatim code requires special care. To allow for verbatim text, the
probsoln package provides the fragile
boolean key
that can be used when defining a problem. If the majority of your
problems require this option, you can set it using:
(This command is provided by the keyval package [12], which is automatically loaded.)
In order to work with verbatim code, the probsoln package
creates a temporary file that's used when the fragile
option is set. The default name for this file is
\jobname
.vrb but if this conflicts with another
package, you can change the extension by redefining
You can also change the basename by redefining
In addition to the answers
and
noanswers
options, you can also show or suppress
solutions using
(to show the solutions) and
(to hide the solutions). These are declarations that can be scoped by placing them within a group.
You can check if the show solutions setting is on or off by testing the
showanswers
boolean flag. This can be done using:
or you can use the \ifthenelse
command provided by the
ifthen package:
or you can use the \ifbool
command provided by the
etoolbox package:
For example:
For longer text, you can use the environments onlyproblem
to only display ⟨text⟩ if the solutions are suppressed, and
onlysolution
to only display ⟨text⟩ if the solutions are displayed. In both
cases, the optional argument ⟨option⟩ may be the
fragile
boolean key, described above.
Example:
\begin{onlyproblem} What is the main drawback of ray guns? \end{onlyproblem} \begin{onlysolution} Overheating. \end{onlysolution}
If the solutions are displayed, only the solution (“Overheating.”) will be typeset, otherwise only the question (“What is the main drawback of ray guns?”) will be typeset.
Note that spaces at the start of the environment are discarded but spaces at the end of the environment aren't. So the EOL character immediately before “What” is discarded (and similarly for the EOL character immediately before “Overheating”) but the EOL character after the question mark is interpreted as a space (and similarly for the EOL character after the full stop in the solution). If these spaces are unwanted, you can suppress them with the % comment character:
\begin{onlyproblem} What is the main drawback of ray guns?% \end{onlyproblem}% \begin{onlysolution} Overheating.% \end{onlysolution}
If you want the question to always appear, regardless of the
showanswers
flag, then don't put it inside the
onlyproblem
environment:
You may prefer to put the solution inside the solution
environment:
which is equivalent to:
where \solutionname
defaults to “Solution”.
Example:
What is the main drawback of ray guns? \begin{onlysolution} \begin{solution} Overheating. \end{solution} \end{onlysolution}
The combination of the onlysolution
and solution
environments in this manner is analogous to the exam class's
solution
environment. Since it's possible that you may
want to use the probsoln package with the exam class,
if probsoln detects that the solution
environment is
already defined, it doesn't try defining its own
solution
environment. This means that if the
above example was used with both the exam class and the
probsoln package, there's a level of redundancy with the
exam class performing a check for its own show/hide solution
setting in its solution
environment and the probsoln
package performing a similar check for its showanswers
flag in its onlysolution
environment.
Therefore, if you are using both exam and probsoln,
I recommend you redefine the solution
environment in
a manner similar to probsoln's solution
environment and just use probsoln's showanswers
flag, as shown in Example 44.
The probsoln package provides an inline numbered list environment
called textenum
:
This uses the same counter and label as the enumerate
environment
would use at the current level. For example, if the
textenum
environment was used outside the
enumerate
environment, the enumi
counter will be used
with \labelenumi
, but if textenum
was used inside
a single enumerate
environment, the enumii
will be used with \labelenumii
.
As with the standard list environments, each item is started with
\item
but no paragraph break is inserted unless you explicitly
add one (either via a blank line or \par
). You can also use
in place of \item
to indicate a correct choice and
in place of \item
to indicate an incorrect choice.
If the solutions are suppressed, these two commands behave the same
as \item
. When the solutions are displayed, the default behaviour of \correctitem
is to
put a frame around the item marker, and the default behaviour of
\incorrectitem
is to just display the marker (as per
\item
). You can change this by redefining
which governs the format used by \correctitem
, and
which governs the format used by \incorrectitem
.
This example uses the exam class with the probsoln
package. The exam class's solution
environment is
redefined to make it more like the probsoln package's
solution
environment. The \ignorespaces
and \ignorespacesafterend
commands (introduced in
Volume 1) suppress any spaces following the start and
end of the environment that may be introduced by a spurious
EOL character. The \noindent
command (also introduced in
Volume 1) suppresses the paragraph indentation.
With this redefinition of the solution
environment, the
exam class's \printanswers
command no longer has an effect.
Instead the probsoln package's \showanswers
command
should be used.
Since the textenum
environment is an inline list, its
items may be placed inside a tabular
environment, as is
done in the example document below.
\documentclass[addpoints]{exam} \usepackage{probsoln} \showanswers \renewenvironment{solution}% {\par\noindent\textbf{\solutionname}: \ignorespaces}% {\ignorespacesafterend} \renewcommand*{\theenumi}{\Alph{enumi}} \begin{document} \begin{center}\bfseries Assignment~1\ifshowanswers\space (Solution Sheet)\fi \end{center} \begin{questions} \question[1] What is the main drawback of ray guns? \begin{onlysolution} \begin{solution} Overheating. \end{solution} \end{onlysolution} \question[1] Which of the following is an ingredient of mind-controlling cookies? \begin{center} \begin{textenum} \begin{tabular}{ll} \incorrectitem arsenic & \incorrectitem cyanide \\ \incorrectitem curare & \correctitem secret genetically modified sugar beet \end{tabular} \end{textenum} \end{center} \end{questions} \end{document}
The resulting text is:
Thus far I haven't shown you anything that the exam class can't
already do, so let's now look at how to define problems and their
associated solutions for later use. A problem can be defined using
the defproblem
environment
where ⟨text⟩ may include the onlyproblem
or
onlysolution
environments (or a combination of both).
The first optional argument ⟨n⟩ is the number of arguments this
problem may take. Each argument may be referenced in ⟨text⟩
using the standard #1 etc method of
referencing an argument. The second optional argument ⟨default
args⟩ are the default arguments to use with this problem when it is
automatically used by \thisproblem
, described below. (The
default argument is ignored when the problem is referenced with
\useproblem
, which must have the arguments explicitly added.)
If ⟨n⟩ is omitted, 0 is assumed and
⟨default args⟩ should also be omitted. The final optional
argument ⟨option⟩ may be used to specify the fragile
boolean key described earlier. If ⟨text⟩ contains any instances of
the onlyproblem
or onlysolution
environments, they will inherit the fragile
state from
defproblem
.
The only mandatory argument is ⟨label⟩, which is a label that uniquely identifies this problem so that it can later be referenced. As with all the other labelling systems described in this book, the label must not contain any special characters. The problem must be defined before use, typically either in the preamble or in an external .tex file.
Example:
Here's a simple example that doesn't require any arguments:
\begin{defproblem}{raygun} \begin{onlyproblem} What is the main drawback of ray guns?% \end{onlyproblem}% \begin{onlysolution} Overheating.% \end{onlysolution} \end{defproblem}
Here's an example that requires one argument. This argument defaults
to 2. Note that the braces { }
are required for each
argument.
\begin{defproblem}[1][{2}]{diffsin} \begin{onlyproblem} Differentiate $f(x) = \sin(#1x)$. \end{onlyproblem}% \begin{onlysolution} $f'(x) = #1\cos(#1x)$ \end{onlysolution} \end{defproblem}
In both of the above examples, I'm assuming that the solutions will
be printed later in the document, separate from the question, so
I haven't bothered to use the solution
environment, since the “Solution:” tag is now redundant. If, on
the other hand, I want a solution sheet that displays the solution
with its associated question, then it's better to remove the
onlyproblem
environment and add the
solution
environment inside the
onlysolution
environment:
\begin{defproblem}{raygun} What is the main drawback of ray guns? \begin{onlysolution} \begin{solution} Overheating.% \end{solution} \end{onlysolution} \end{defproblem}
Since it's quite cumbersome having to write so many \begin
and \end
commands, the probsoln package provides
a convenient shortcut command:
This is equivalent to:
\begin{defproblem}[⟨n⟩][⟨default args⟩]{⟨label⟩}% ⟨problem⟩% \begin{onlysolution}% \begin{solution}% ⟨solution⟩% \end{solution}% \end{onlysolution}% \end{defproblem}
There is also a starred version:
which is a shortcut for:
Note that both versions of \newproblem
don't support verbatim, so if
your problem contains verbatim text you must use the defproblem
environment (with the fragile
key set).
Once you have defined your problems, you can the use them in your document. You can explicitly use a particular problem via the command:
where ⟨label⟩ is the label uniquely identifying the problem. If the problem was defined to have arguments, the arguments must then follow the label. The optional argument ⟨dataset⟩ indicates the dataset in which the problem is stored. If omitted the default dataset is assumed.
Example:
Given the definitions in the earlier example of the problems with the labels raygun and diffsin, these can now be used in the document:
This is on the assumption that the problems were defined in the document preamble, which will automatically place them in the default dataset. Since the diffsin problem was defined to have one argument, that argument has to be provided.
If you define all your problems in external files, you can input
each file using LaTeX's standard \input
command, which will
have the same effect as just defining all those problems within the
document. Alternatively, you can load the problems into a particular
dataset using one of the commands described below, where
⟨dataset⟩ is the name of the dataset and ⟨filename⟩ is the
name of the file. If the dataset is omitted, the default dataset is
assumed. Note that the dataset name mustn't contain any
special characters.
This loads all the problems defined in the given ⟨filename⟩ and adds them to the ⟨dataset⟩ indicated in the optional argument.
This only defines the problems listed in the comma-separated list ⟨labels⟩. The other problems in ⟨filename⟩ are ignored.
This is the reverse of \loadselectedproblems
. It only defines
the problems that aren't listed in the comma-separated list ⟨exception
list⟩.
This loads ⟨n⟩ randomly selected problems defined in ⟨filename⟩ and adds them to the given dataset.
Similar to the previous command but discounts the problems listed in ⟨exception list⟩ when making the random selection.
Once you have loaded your problems, using one of the above commands, you can iterate through a dataset using:
This does ⟨body⟩ at each iteration. Within ⟨body⟩ you can use
to use the current problem and
to access the current problem label. Unlike \useproblem
, you
don't supply the problem arguments when you use \thisproblem
. If
the problem requires one or more arguments and no default arguments
were provided when the problem was defined or the
usedefaultargs
package option wasn't used, then
you will be prompted for the arguments, which requires LaTeX to be
run in interactive mode. (Interactive mode is when LaTeX stops on
encountering an error and prompts you for a response.)
Example:
To iterate through all problems in the default dataset:
Assuming that you haven't switched on the solutions using
\showanswers
or the answers
package option,
this will just list all the problems. If you switch on the
solutions, this will include the solutions but omit any text inside
the onlyproblem
environment.
There is also a similar command:
which behaves like \foreachproblem
but only iterates over
those problems that contain an onlysolution
environment.
(You must have first used the problems earlier in the document.)
Note that you still need to switch on the show solution flag using
\showanswers
or the answers
package option
if you want the solutions displayed.
This iterates over all the defined datasets and does ⟨body⟩ at each iteration. Within ⟨body⟩ you can use the control sequence ⟨cs⟩ to access the current dataset name.
For example, to display all problems in all datasets:
\begin{enumerate} \foreachdataset{\thisdataset}% {% \foreachproblem[\thisdataset]{\item\thisproblem}% } \end{enumerate}
Example:
Suppose I have some calculus problems defined in a file called calculus.tex and I have some linear algebra problems defined in a file called linearalgebra.tex, then in my document preamble I could, say, load five randomly selected calculus problems and four randomly selected linear algebra problems using:
\loadrandomproblems[calculusproblems]{calculus} \loadrandomproblems[linearalgebraproblems]{linearalgebra}
(The .tex extension may be omitted.) This creates two
datasets with the labels calculusproblems and
linearalgebraproblems. In my document, I could simply
iterate over all datasets using \foreachdataset
as described
above.
\begin{enumerate} \foreachdataset{\thisdataset}% {% \foreachproblem[\thisdataset]{\item\thisproblem}% } \end{enumerate}
Alternatively, I might want to divide the document into sections for each topic:
\section{Calculus} \begin{enumerate} \foreachproblem[calculusproblems]{\item\thisproblem} \end{enumerate} \section{Linear Algebra} \begin{enumerate} \foreachproblem[linearalgebraproblems]{\item\thisproblem} \end{enumerate}
The seed used by the pseudo random number generator can be changed using:
where ⟨n⟩ is a non-zero number to use as the seed. Random numbers can be generated using:
which generates a random integer between 1 and ⟨n⟩ (inclusive) and stores it in the given count register (see §2.1.3 Arithmetic) or
which generates a random integer between ⟨min⟩ and ⟨max⟩ (inclusive) and stores it in the LaTeX counter whose name is ⟨counter⟩. (See also §9.5 Random Numbers below.)
Example:
Recall the earlier diffsin problem. Suppose that instead of defining the problem to have an argument for the factor, the factor is randomly selected instead. First a new register needs to be defined:
\newcount\myrandarg
Now the problem can be defined:
\begin{defproblem}{randdiffsin} \PSNrandom{\myrandarg}{10}% Differentiate $f(x) = \sin(\the\myrandarg x)$. \begin{onlysolution} $f'(x) = \the\myrandarg\cos(\the\myrandarg x)$ \end{onlysolution} \end{defproblem}
If the random number seed is set to the current year:
then this problem will appear differently if the same document is rebuilt on different years.
\begin{defproblem}{randdiffsin} \ifundef\randdiffsinarg {% \PSNrandom{\myrandarg}{10}% \xdef\randdiffsinarg{\the\myrandarg}% globally store } {}% Differentiate $f(x) = \sin(\randdiffsinarg x)$. \begin{onlysolution} $f'(x) = \randdiffsinarg\cos(\randdiffsinarg x)$ \end{onlysolution} \end{defproblem}
(See also §9.5 Random Numbers.)
You can iterate over ⟨n⟩ randomly selected items in a comma-separated list using:
This performs ⟨body⟩ at each iteration where ⟨cs⟩ is a control sequence set to the currently selected item.
Example:
Suppose you have four files called file1.tex, file2.tex, file3.tex and file4.tex that all contain problem definitions, but you only want to load the problems defined in two of those files selected at random:
\doforrandN {2}% randomly select 2 items from the list {\thisfile}% command in which to store the current item {file1,file2,file3,file4}% the list {\loadallproblems{\thisfile}}
Example:
In this example, only one random selection is made and the selected item is saved for later use:
\doforrandN{1}{\thisitem}{cow,duck,chicken} {\global\let\thesubject\thisitem}% \doforrandN{1}{\thisitem}{road,field,river} {\global\let\theobject\thisitem}% Why did the \thesubject\␣cross the \theobject? \begin{onlysolution} \begin{solution} So that the \thesubject\␣could get to the other side of the \theobject. \end{solution} \end{onlysolution}
The previous warning also applies here if you intend to display the solutions in another part of the document. In this case, a similar approach can be used:
\begin{defproblem}{whycross} \ifdef{\thesubject} {}% already defined {% \doforrandN{1}{\thisitem}{cow,duck,chicken} {\global\let\thesubject\thisitem}% \doforrandN{1}{\thisitem}{road,field,river} {\global\let\theobject\thisitem}% }% \begin{onlyproblem} Why did the \thesubject\␣cross the \theobject? \end{onlyproblem} \begin{onlysolution} So that the \thesubject\␣could get to the other side of the \theobject. \end{onlysolution} \end{defproblem}
This uses the \ifdef
command provided by the etoolbox
package described in §2.1.1 Macro Definitions.
Note that the pgfmath package also provides pseudo-random commands, which you may prefer to use. Some of these are described in §9.5 Random Numbers.
In this example, I have a number of files containing problem definitions:
- mth101.tex
This file contains 10 easy differentiation problems in the form:
\begin{defproblem}{diff:sinx/x} \begin{onlyproblem}% $y = \frac{\sin x}{x}$. \end{onlyproblem} \begin{onlysolution}% \[\frac{dy}{dx} = \frac{\cos x}{x} - \frac{\sin x}{x^2}\] \end{onlysolution}% \end{defproblem}
You can download the complete file.
- problems-1stprinciples.tex
This file contains 5 differentiation from first principle problems in the form:
\begin{defproblem}{dfp:cons}% \begin{onlyproblem}% Differentiate from first principles $f(x) = c$ where $c$ is a constant.% \end{onlyproblem}% \begin{onlysolution}% \begin{align*} \frac{df}{dx} & = \lim_{\Delta x\rightarrow 0}\frac{c-c}\Delta x\\ & = \lim_{\Delta x\rightarrow 0}0\\ & = 0 \end{align*}% \end{onlysolution}% \end{defproblem}
You can download the complete file.
I can now write a document that randomly selects 3 problems from the first file and 1 problem from the second file. The questions are listed first and the solutions later:
\documentclass{article} \usepackage{amsmath} \usepackage{probsoln} \loadrandomproblems{3}{mth101} \loadrandomproblems{1}{problems-1stprinciples} \begin{document} \section{Differentiation Problems} \begin{enumerate} \foreachproblem{\item\thisproblem} \end{enumerate} \section{Solutions} \showanswers \begin{enumerate} \foreachsolution{\item\thisproblem} \end{enumerate} \end{document}
This book is also available as A4 PDF or 12.8cm x 9.6cm PDF or paperback (ISBN 978-1-909440-07-4).