26

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.

9
  • 2
    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, 2014 at 17:09
  • 1
    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, 2014 at 17:10
  • 1
    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, 2014 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, 2014 at 17:14
  • 2
    exsheets all the way
    – jub0bs
    May 2, 2014 at 17:45

4 Answers 4

13

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:

enter image description here

\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
    \advance\count@\@ne
  \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
      \advance\count@\@ne
  \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 ***.

3
  • 1
    I'd do something like this with a scripting language, like Perl or Python
    – vonbrand
    May 3, 2014 at 10:44
  • @werner: Could you please tell me why I need to wait about 30 seconds to generate another set of questions? Dec 14, 2014 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. Mar 22, 2020 at 3:05
9

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:
            questions.append(qfh.read())

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.

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

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.

3
  • 2
    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, 2017 at 23:49
  • 1
    But he informs that the package does what the poster wants. Isn't that a clear solution?
    – djnavas
    Nov 16, 2017 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, 2020 at 4:07
0

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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .