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 – jub0bs 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 https://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
• @Werner: What if I only need to get an nth sentence in bankA? for example: \ getquestionsfrombank {bankA.tex} {6}, then we only get the 6th question. – Jack Nguyễn Mar 22 '20 at 3:05

Maybe I'm late to the party. I found this old thread while searching for a way to automate the creation of random exams. As suggested by @vonbrand a Python script can do the trick. This is the solution I came up with:

import sys
import os
import random

exam = sys.argv[1]
folder = sys.argv[2]

questions = []
for bank, number in zip(sys.argv[3::2], map(int, sys.argv[4::2])):
qfiles = [
os.path.join(dirpath, fname)
for dirpath, dirnames, filenames in os.walk(os.path.join(folder, bank))
for fname in filenames
if os.path.splitext(fname)[-1].lower() == '.tex'
]
for qfile in random.sample(qfiles, number):
with open(qfile, 'r') as qfh:

with open(exam, 'w') as efh:
for question in questions:
print(question, file=efh)


Demo

Let us assume we have 3 question banks, namely ch1, ch2 and ch3, with 5, 10 and 100 mock questions, respectively, in accordance with the following file tree:

C:
├── exams
└── banks
├── ch1
│   ├── ch1q1.tex
│   ├── ...
│   └── ch1q5.tex
├── ch2
│   ├── ch2q1.tex
│   ├── ...
│   └── ch2q10.tex
└── ch3
├── ch3q1.tex
├── ...
└── ch3q100.tex


If we wish to create an exam by randomly picking one question from ch1, one question from ch2 and two questions from ch3 we can get the job done by typing the following instruction in the command line:

C:\>python exam.py C:\exams\sample_exam.tex C:\banks ch1 1 ch2 1 ch3 2


And this is how the resulting file sample_exam.tex would look:

\question Chapter 1 -- Question 4
\begin{choices}
\choice Choice a
\CorrectChoice Choice b
\choice Choice c
\choice Choice d
\end{choices}

\question Chapter 2 -- Question 8
\begin{choices}
\choice Choice a
\choice Choice b
\CorrectChoice Choice c
\end{choices}

\question Chapter 3 -- Question 75
\begin{choices}
\CorrectChoice Choice a
\choice Choice b
\choice Choice c
\choice Choice d
\choice Choice e
\end{choices}

\question Chapter 3 -- Question 23
\begin{choices}
\choice Choice a
\choice Choice b
\CorrectChoice Choice c
\end{choices}


It is important to point out that you can put as many <bank_i> <number_i> pairs in the command line argument list as question banks you have. It is also worth noticing that nothing prevents you from organizing your question banks into sections (subdirectories):

C:
├── exams
└── banks
├── ch1
│   ├── ch1q1.tex
│   ├── ...
└── ch2
├── sec1
│    ├── ch2sec1q1.tex
│    ├── ch2sec1q2.tex
│    ├── ...
├── sec2
│    ├── ch2sec2q1.tex
│    ├── ch2sec2q2.tex
│    ├── ...
├── ...


as the question search is performed recursively.

• 'banks' is a subfolder of 'exams'? – pluton Feb 19 at 16:57

I'm not sure if you are still looking for such a package, but if so, check out the probsoln package: https://ctan.org/pkg/probsoln?lang=en. I've been using it for the last few years, and I think that it does exactly what you want.

• Welcome to TeX.SE!. In order to improve your question, I kindly suggest you to explain in more detail the features that this package has, related to the requirements of the Orginal Porster of the question and for us to know about this important contribution you are making. – Cragfelt Nov 15 '17 at 23:49
• But he informs that the package does what the poster wants. Isn't that a clear solution? – djnavas Nov 16 '17 at 3:28
• @djnavas It would be helpful to include a sample showing how it works. As things stand, the answer just tells us to use a certain package. We still have to figure out how to use the package. If this included a minimal working example, then people could get started that much faster. – Teepeemm Mar 22 '20 at 4:07

I have ever coded a code to randomly select equations from "banks" and randomize almost all details in test papers. The code is available for free download at: https://github.com/LiuGangKingston/Personalized-Test-Creator.git

Specifically, it can randomize

1. choosing questions from "question banks";
2. choosing choices from "choice banks" of multiple-choice questions;
3. the sequence of given questions or grouped questions;
4. the sequence of choices of multiple-choice questions;
5. any integer or real or other numbers in computational questions; and more.

Of course, the teacher can absolutely control the way and the extent of the randomization. As a result, the teacher will get as many as test papers he/she wants. At the same time, the answers and/or solutions to any question in any generated paper will also be generated in separated files. In order to use it, the teacher should supply one original test paper in Latex with some special commands. The special command "PTC-RRR(2.00, 5.51, .25)", as an example, will be randomly replaced by one of (2.00, 2.25, 2.50, 2.75, 3.00, 3.25, 3.50, 3.75, 4.00, 4.25, 4.50, 4.75, 5.00, 5.25, 5.50) numbers. Please note that all of them have two digits after the decimal point, based on the usage of the command. The downside is that it is written in the FORTRAN language, then for generating answers, the teacher should code a little bit FORTRAN as well. Since for purpose of answers, only a very limited straightforward programming subset of FORTRAN language is needed. For example, Variable_A = Variable_B + Variable_C * sin(Variable_D) / Variable_E + Variable_F ** Variable_G to calculate the result and save it to Variable_A with other variables. Then all of them will be directly accessible for writing answers in the Latex files.

The last point, it is coded for general Latex. Currently, the form of the test paper is totally designed by the teacher but can be in any way. Later versions may fully support exam document class for more convenience.