Monday, February 25, 2008

Deferred printing in LaTeX

Ever written a textbook? Ever written any document that has questions/problems and answers to those problems? Usually when you do this, you want to have questions printed in one place (or questions in each section of the document), and answers printed at the end of the document (so your readers, which are usually students, would try to actually solve the problem rather than just look at the solution which is right under the problem).
Now, I don't know how most of you usually do this, but up until now the only way I knew to do this is the "brute force" approach---I would simply write the questions and then, at the end of the document, I would write the answers. The obvious drawback of this approach is that you have to look up each individual question when writing the answers (just to see what it was) and this can quickly become very boring. Or, if you have the questions/answers on paper, they are usually written in pairs question/answer so you have to first type all the questions (ignoring the answers), and then go back to the begining of the paper and type all the answers (ignoring the questions). Anyway, I hope you get the picture why I hate doing this.
Wouldn't it be great if I could somehow have the question and its answer in the same place in the source of the document (notice that we are not talking about WYSIWYG text processors here), but then defer the printing of answers in the output document (that is, print the answers at the end of the document)? The solution to my problem comes in a form of the LaTeX box mechanism. Just take a look at this minimal example:


\documentclass{article}

\newbox\answerscollect
\newcounter{problem}
\setcounter{problem}{0}
\renewcommand{\theproblem}{\textit{Problem \arabic{problem}.}\,}

\def\answer#1{\par\setbox\answerscollect=\vbox{\unvbox\answerscollect\vspace{5mm}\theproblem\,#1\par}}
\def\printanswers{\unvbox\answerscollect\setbox\answerscollect=\vbox{}}
\def\initbox{\setbox\answerscollect=\vbox{Answers:\par}}

\newcommand{\problem}[1]{\addtocounter{problem}{1}\par\theproblem\textit{#1}}

\begin{document}

\initbox

\section{Intro}
Some text before...

\problem{What is the capital of the US?}
\answer{Washington, D.~C.}

\problem{What is 2+2?}
\answer{4}

\section{Foo}

Some other text goes here...

\section{Answers}

\printanswers

\end{document}

Now I know it looks a bit complicated, but it really isn't. What we are doing is creating a box and storing all the answers in it, and then, when the time is write, we simply print all the contents of that box using the \printanswers command. This way, we can simply have the problem and its solution (answer) in the same place in the source of the document, but have the answers appear in a totally different place in the output pdf (or postscript or whatever). Now, I'm not going to go into details of the LaTeX code itself here. Those who know LaTeX will pretty much understand the code, and those who don't should first learn the basics before trying to get this. Anyway, the code can be used out-of-the-box; if someone would like to change something but doesn't know how, you can contact me. Last, but not least, here is how the output pdf looks like:


UPDATE: As Evan noted, if you are going to use multiple files, don't use \include. Instead, use the \input command which does not create new .aux files.






9 comments:

Jim said...

Rolling your own can be a lot of fun, but FYI there are a number of pre-made solutions to this problem on CTAN http://www.ctan.org .

Here are some package names that might be worth looking at.
exercise
exams
pbsheet
exerquiz
examdesign
assignment
mathexam
uebungsblatt
qcm
exam
answers
probsoln
They don't all do exactly what you want, but they are close.

One more comment: I dealt with this problem also in my text at http://joshua.smcvt.edu/linearalgebra (with a bit more, such as only printing some of the answers, like the odd-numberd ones) and the LaTeX source is available on that page.

Jim

Filox said...

It is good that you mentioned these packages, as I forgot to mention them. Of course, there are always pre-made solutions for almost everything that one needs, but I am always trying to build my own solutions from nothing. This way, I can completely control what goes on and customize the solution to fit my particular needs. The idea of this (and my other TeX-related posts) is to show how how to build something from nothing, not use the already available solutions. However, in real applications I agree that sometimes using one of the mentioned packages is easier/better.

Evan said...

Building this from scratch was exactly what I needed, because none of the packages allowed me to do precisely what I wanted.

Just a heads up, though. For some reason using \include breaks the functionality. Instead, use \input, which doesn't create new .aux files. I'm not 100% sure why this problem arises, but I know that it took me a few good hours to figure out the fix. I thought I'd leave a note here for future users.

Filox said...

Evan, thanks for the heads up! I'll soon update the post with this new information.

Krzysztof said...

Your solutions is quite good, but IMHO it could be done better by using TeX tokens registers.

The width of vbox in your code is the same as text width at the time of \initbox so later when you use \printanswers the horizontal space must be the same (or at least not smaller). I think this is a minor problem, but another one I found is that inter-line spacing is broken if you use \unvbox. Just put some text before \printanswers or remove \vspace{5mm} to see it-lines will appear one just above another.

My solutions works as if all tokens where put exactly where they should be in source file when doing the task manually (no macros). The advantage is that inter-line spacing is correct and text has proper width and in-paragraph page breaks work (for longer answers). I've also done some tweaks (\noindent, \enspace, \stepcounter).

Example how to use TeX tokens register may be found e.g. on StackOverflow.

\newtoks\answerscollect
\newcounter{problem}
\setcounter{problem}{0}
\renewcommand{\theproblem}{\textit{Problem \arabic{problem}.}\enspace}

\def\answer#1{\par\answerscollect=\expandafter{%
\the\answerscollect\vspace{.5\baselineskip}\noindent\theproblem #1\par}}
\def\printanswers{\the\answerscollect\answerscollect={}}
\def\initbox{\answerscollect={\par\noindent Answers:\par}}

\newcommand{\problem}[1]{\stepcounter{problem}\par\noindent\theproblem\textit{#1}}

Krzysztof said...

Sorry for my previous post, there is a mistake which I didn't spot: every answer has the same number in my code, because \theproblem is expanded only in \printanswers--which is too late. Here is a corrected version, but it's a bit ugly. Anyone knows how to do it better?

\newtoks\answerscollect
\newcounter{problem}
\setcounter{problem}{0}
\renewcommand{\theproblem}{\textit{Problem \arabic{problem}.}\enspace}

\def\answer#1{%
\edef\answertmp{%
\the\answerscollect% \the\answerscollect
\noexpand\vspace{.5\noexpand\baselineskip}% \vspace{.5\baselineskip}
\noexpand\noindent% \noindent
\expandafter\noexpand\theproblem% \theproblem
#1% #1
\noexpand\par% \par
}%
\par\answerscollect=\expandafter{\answertmp}%
}
\def\printanswers{\the\answerscollect\answerscollect={}}
\def\initbox{\answerscollect={\par\noindent Answers:\par}}

\newcommand{\problem}[1]{\stepcounter{problem}\par\noindent\theproblem\textit{#1}}

Krzysztof said...

Ok, finally found an elegant solution!:) The problem with previous solution was the expansion of LaTeX commands. \textit in \theproblem was protected using
\expandafter\noexpand\theproblem
but if one changed the definition of \theproblem by inserting some additional command, that protection would not suffice and the only solution would be to precede every command except \arabic (which we want to be expanded early) with \noexpand. But according to TeX FAQ LaTeX command are protected if we replace \edef by \protected@edef. Here is the complete code:

\makeatletter
\newtoks\answerscollect
\newcounter{problem}
\setcounter{problem}{0}
\def\theproblem{\textit{Problem \arabic{problem}.}\enspace}

\def\answer#1{%
\protected@edef\answertmp{%
\the\answerscollect\vspace{.5\baselineskip}\noindent\theproblem#1\par}%
\par\answerscollect=\expandafter{\answertmp}}
\def\printanswers{\the\answerscollect\answerscollect={}}
\def\initbox{\answerscollect={\par\noindent Answers:\par}}

\newcommand{\problem}[1]{\stepcounter{problem}\par\noindent\theproblem\textit{#1}}
\makeatother

Liam Devlin said...

\newenvironment{questions}{\begin{enumerate}}{\end{enumerate}}
% \newenvironment{question}{\item}{}
% \newenvironment{answers}{\item}{}

Is it possible to do deferred printing but using environments like
\begin{questions}
\begin{question}
\begin{answer}
\end{answer}
\end{question}
\end{questions}

Alex Goldvard and Lavi Karp said...

How to change your code in order to deal with poroblems that have subproblems, like 1a, 1b..?

Alex