# Generate exam from a question bank?

I am looking for a LaTeX package that will allow me to generate an exam with questions drawn from a particular question bank. Each question within the bank would be a self-contained block of LaTeX code.

For example, in the spirit of the exam package, I might have the questions:

\question
$2+2=$
\begin{choices}
\choice 3
\choice 0
\choice 4
\choice $\sqrt{2}$
\choice $-\pi$
\end{choices}

\question
$\int_0^1 x^2\,dx=$
\begin{choices}
\choice $-1$
\choice $1/3$
\choice $\infty$
\choice $1/2$
\choice None of the above.
\end{choices}


This would be a bank containing two questions. Each question is a block of LaTeX code that, if it were to be "drawn" from the bank and inserted into a "parent", compilable LaTeX file, would thereby generate an exam (presumably what such a package would do).

Being greedy, I'd really like if I could specify the number of questions $q_1$ to be drawn from question bank $B_1$, $q_2$ from $B_2$, etc. where each bank $B_i$ would be over a specific topic.

If this already exists, I have not been able to find it. Preserving the functionality of the exam documentclass (or something like it) would make assigning points and/or generating answer keys simultaneous with (random) exam creation.

-
I assume, since the question is somewhat general, that you're open to an input adjustment. For example, instead of \question, you could potentially be okay with wrapping each question inside a bank in an environment, like \begin{question}...\end{question}. –  Werner May 2 '14 at 17:09
Sure thing! I am open to anything that works. Also, say I had 20 questions in the bank. It would be important for me to be able to specify that I want, say, 5 questions (pseudo)randomly chosen from the bank. –  JohnD May 2 '14 at 17:10
I have no doubt that a system for generating exams like this could be implemented in LaTeX, but wouldn't a short Perl script accomplish the same thing more simply? –  Senex May 2 '14 at 17:11
Perhaps, but I am not familiar with Perl. My motivation was to take the existing functionality of the exam documentclass and just automate things via a question bank. –  JohnD May 2 '14 at 17:14
exsheets all the way –  Jubobs May 2 '14 at 17:45

Let's assume you have a bank of questions in bankA.tex that has the following format:

\begin{questionblock}
Question 1
\end{questionblock}
\begin{questionblock}
Question 2
\end{questionblock}
\begin{questionblock}
Question 3
\end{questionblock}
\begin{questionblock}
Question 4
\end{questionblock}
\begin{questionblock}
Question 5
\end{questionblock}
\begin{questionblock}
Question 6
\end{questionblock}
\begin{questionblock}
Question 7
\end{questionblock}
\begin{questionblock}
Question 8
\end{questionblock}
\begin{questionblock}
Question 9
\end{questionblock}
\begin{questionblock}
Question 10
\end{questionblock}


It's important to denote each question by some form of block/environment (questionblock in this case). Ultimately you would keep each bank inside a separate file, say bankA.tex, bankB.tex, ... There should be no restriction on what can be included inside the questionblock environment.

Our algorithm for generating a set of random questions from this bank proceeds as follows:

1. Read file and store in a macro (thanks to catchfile;

2. Count how many questions are inside the bank (using environ to process the entire questionblock environment as a \stepcounter mechanism). Call this number totalquestions;

3. Create a random list from 1 to totalquestions (as reference, see Generating random numbers without repetitions));

4. Repeat the following steps:

• Pick a number from the random list;

• Process the entire bank until you reach the question that matches the picked number and print it;

• Prune the random list by removing the selected number.

The above procedure may do a large amount of extra processing, but I'm confident it won't be prohibitively intensive. Here's a complete minimal example for one bank:

\documentclass{article}
\usepackage{multicol}% Just for this example
\usepackage{filecontents}
\begin{filecontents*}{bankA.tex}
\begin{questionblock}
Question 1
\end{questionblock}
\begin{questionblock}
Question 2
\end{questionblock}
\begin{questionblock}
Question 3
\end{questionblock}
\begin{questionblock}
Question 4
\end{questionblock}
\begin{questionblock}
Question 5
\end{questionblock}
\begin{questionblock}
Question 6
\end{questionblock}
\begin{questionblock}
Question 7
\end{questionblock}
\begin{questionblock}
Question 8
\end{questionblock}
\begin{questionblock}
Question 9
\end{questionblock}
\begin{questionblock}
Question 10
\end{questionblock}
\end{filecontents*}

\usepackage{catchfile,environ,tikz}

\makeatletter% Taken from http://tex.stackexchange.com/q/109619/5764
\def\declarenumlist#1#2#3{%
\expandafter\edef\csname pgfmath@randomlist@#1\endcsname{#3}%
\count@\@ne
\loop
\expandafter\edef
\csname pgfmath@randomlist@#1@\the\count@\endcsname
{\the\count@}
\ifnum\count@<#3\relax
\repeat}
\def\prunelist#1{%
\expandafter\xdef\csname pgfmath@randomlist@#1\endcsname
{\the\numexpr\csname pgfmath@randomlist@#1\endcsname-1\relax}
\count@\pgfmath@randomtemp
\loop
\expandafter\global\expandafter\let
\csname pgfmath@randomlist@#1@\the\count@\expandafter\endcsname
\csname pgfmath@randomlist@#1@\the\numexpr\count@+1\relax\endcsname
\ifnum\count@<\csname pgfmath@randomlist@#1\endcsname\relax
\repeat}
\makeatother

% Define how each questionblock should be handled
\newcounter{questionblock}
\newcounter{totalquestions}
\NewEnviron{questionblock}{}%

\newcommand{\randomquestionsfrombank}[2]{%
\CatchFileDef{\bank}{#1}{}% Read the entire bank of questions into \bank
\setcounter{totalquestions}{0}% Reset total questions counters  ***
\RenewEnviron{questionblock}{\stepcounter{totalquestions}}% Count every question  ***
\bank% Process file  ***
\declarenumlist{uniquequestionlist}{1}{\thetotalquestions}% list from 1 to totalquestions inclusive.
\setcounter{totalquestions}{#2}% Start the count-down
\RenewEnviron{questionblock}{%
\stepcounter{questionblock}% Next question
\ifnum\value{questionblock}=\randomquestion
\par% Start new paragraph
\BODY% Print question
\fi
}%
\foreach \uNiQueQ in {1,...,#2} {% Extract #2 random questions
\setcounter{questionblock}{0}% Start fresh with question block counter
\pgfmathrandomitem\randomquestion{uniquequestionlist}% Grab random question from list
\xdef\randomquestion{\randomquestion}% Make random question available globally
\prunelist{uniquequestionlist}% Remove picked item from list
\bank% Process file
}}

\begin{document}

\begin{multicols}{3}
\foreach \x in {1,...,6} {
\bigskip
\randomquestionsfrombank{bankA.tex}{6}
}
\end{multicols}

\end{document}


A call to \randomquestionsfrombank{<file>}{<num>} picks <num> questions at random from <file>. If you don't want to process the <file> the first time around to get the number of questions, you can remove the lines highlighted with ***.

-
I'd do something like this with a scripting language, like Perl or Python –  vonbrand May 3 '14 at 10:44
@werner: Could you please tell me why I need to wait about 30 seconds to generate another set of questions? –  Vahid Damanafshan Dec 14 '14 at 11:36