16

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.

  • 1
    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
  • 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 '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
  • 2
    exsheets all the way – jub0bs May 2 '14 at 17:45
11

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 ***.

  • 1
    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
4

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.

  • 1
    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
2

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 1 question from ch1, 1 question from ch2 and 2 questions from q3 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.