Is there an environment (or macro for itemize) that shuffles all items randomly each time the pdf is generated?
-
Although it doesn't have an answer, the example in this question might be of some help: Problem randomizing answers in eqexam.– Alan MunnFeb 3, 2013 at 18:43
4 Answers
FWIW, ConTeXt supports this out of the box. Simply add the random
key to \startitemize
. (In ConTeXt, \startitemize[n]
is equivalent to \begin{enumerate}
of LaTeX).
\starttext
\startitemize[n,random]
\startitem One \stopitem
\startitem Two \stopitem
\startitem Three \stopitem
\startitem Four \stopitem
\startitem Five \stopitem
\startitem Six \stopitem
\stopitemize
\stoptext
which gives:
The random seed is stored in the tuc file so that you get the same output each time the file is compiled (otherwise, multiple compiles can lead to an infinite cycle and you may not get all the references correct). So you need to delete the tuc file to change the random seed, and hence get a different output.
-
1You can also set the random seed without deleting the tuc file. See: wiki.contextgarden.net/Random Oct 5, 2013 at 18:44
To produce randomly ordered lists I have defined a new environment, randomList
, that is used in almost exactly the same way as the enumerate
environment and its friends. The code can be used with enumerate
, itemize
and description
environments and, in fact, any similar "list environment" such as, for example, those created using the enumitem package.
The code requires there to be a blank line (a \par
) after every item, including the very last item in the list. For example, the following code will randomly permute the three list items:
\begin{randomList}
\item First item
\item Second item
\item Third item
\end{randomList}
The list items can have multiple lines of text. Paragraphs need to be enclosed in braces.
The basic idea is to redefine \item
so that it "slurps up" everything that follows an \item
up to the next \par
, which is then saved as a macro called \randomListItem@k
, for item number k
. As the list is "read" in, a random permutation of the list indices 1,2,...,n
is constructed. Finally, at the end of the environment the items are printed in the order given by this random permutation using \forlistloop
from the etoolbox.
There doesn't seem to be an easy/known way/package to generate a random permutation of 1,2,...,n
and, in fact, the bulk of the code below is devoted to doing this. The random permutation is constructed by recursively inserting n
into a random permutation of 1,2,...,n-1
. When n
is inserted it is randomly placed into an existing sequence of length n-1
using \pgfmathparse{random(1,n)}
, from the pgfmath package. In the code below the random permutation is built at the same time as the \randomListItem@k
items are constructed by using the macro \randomlyInsertInList
. This code could be used to construct a random permutation of an arbitrary (comma separated) list, but I haven't tested this. (Also, note that the permuted list is an etoolbox list rather than a CSV list.)
Here is a MWE that gives its definition of the code needed for the randomList
environment together with some examples to show it is used. As mentioned already, for the code to work there has a to be a blank line following each item.
\documentclass{article}
\usepackage{etoolbox}
\usepackage{pgfkeys}
\usepackage{pgfmath}
% code for generating a random permutation
\newcounter{randomListLength}% current length of our random list
\newcounter{randomListPosition}% current list index
\newcounter{newRandomListElementPosition}% position to insert new element
% insert #1 into the next position of \newRandomList unless the position
% index \randomListPosition is equal to \newRandomListElementPosition in
% which case the \newRandomListElement is added first
\newcommand\randomlyInsertElement[1]{%
\stepcounter{randomListPosition}%
\ifnum\value{randomListPosition}=\value{newRandomListElementPosition}%
\listxadd\newRandomList{\newRandomListElement}%
\fi%
\listxadd\newRandomList{#1}%
}
% \randomlyInsertInList{list name}{new list length}{new element}
\newcommand\randomlyInsertInList[3]{%
\pgfmathparse{random(1,#2)}%
\setcounter{newRandomListElementPosition}{\pgfmathresult}%
\ifnum\value{newRandomListElementPosition}=#2\relax%
\listcsxadd{#1}{#3}%
\else%
\def\newRandomList{}% start with an empty list
\def\newRandomListElement{#3}% and the element that we need to add
\setcounter{randomListPosition}{0}% starting from position 0
\xdef\currentList{\csuse{#1}}
\forlistloop\randomlyInsertElement\currentList%
\csxdef{#1}{\newRandomList}%
\fi%
}
% define some pgfkeys to allow key-value arguments
\pgfkeys{/randomList/.is family, /randomList,
environment/.code = {\global\letcs\beginRandomListEnvironment{#1}
\global\letcs\endRandomListEnvironment{end#1}
},
enumerate/.style = {environment=enumerate},
itemize/.style = {environment=itemize},
description/.style = {environment=description},
seed/.code = {\pgfmathsetseed{#1}}
}
\pgfkeys{/randomList, enumerate}% enumerate is the default
% finally, the code to construct the randomly permuted list
\makeatletter
\newcounter{randomListCounter}% for constructing \randomListItem@<k>'s
% \useRandomItem{k} prints item number k
\newcommand\useRandomItem[1]{\csname randomListItem@#1\endcsname}
% \setRandomItem{k} saves item number k for future use
% and builds a random permutation at the same time
\def\setRandomItem#1\par{\stepcounter{randomListCounter}%
\expandafter\protected@xdef\csname randomListItem@\therandomListCounter\endcsname{\noexpand\item#1}%
\randomlyInsertInList{randomlyOrderedList}{\therandomListCounter}{\therandomListCounter}%
}%
\let\realitem=\item
\makeatother
\newenvironment{randomList}[1][]{% optional argument -> pgfkeys
\pgfkeys{/randomList, #1}% process optional arguments
\setcounter{randomListLength}{0}% initialise length of random list
\def\randomlyOrderedList{}% initialise the random list of items
% Nthing is printed in the main environment. Instead, \item is
% used to slurp the "contents" of the item into randomListItem@<counter>
\let\item\setRandomItem%
}
{% now construct the list environment by looping over the randomly ordered list
\let\item\realitem
\setcounter{randomListCounter}{0}
\beginRandomListEnvironment\relax
\forlistloop\useRandomItem\randomlyOrderedList
\endRandomListEnvironment
}
% test compatibility with enumitem
\usepackage{enumitem}
\newlist{Testlist}{enumerate}{1} %
\setlist[Testlist]{label*=\alph*.}
\setlist{nosep}\parindent=0pt% for more compact output
\begin{document}
\textbf{Enumerate example:}
\begin{randomList}
\item First item
\item Second item
\item Third item
\end{randomList}
\textbf{Itemise example:}
\begin{randomList}[itemize]
\item First item
\item Second item
\item Third item
\end{randomList}
\textbf{Description example}
\begin{randomList}[description]
\item[D1] First item
\item[D2] Second item
\item[D3] Third item
\end{randomList}
\textbf{Testlist example with seed}
\begin{randomList}[environment=Testlist, seed=4]
\item First item
\item Second item
\item Third item
\end{randomList}
\end{document}
This (currently) produces the following output (but is subject to change based on the time the code is run, due to the random sorting):
By default, randomList
randomly permutes the items in an enumerate
environment. As shown in the MWE, an optional argument of itemize
or description
will make randomList
use these two environments (if two or more of these are given then the last one wins). There is another optional argument of seed=<random seed>
for setting the random seed used by \pgfmathparse{random(*)}
. This simply calls \pgfmathsetseed
.
As the last example shows, the code is compatible with the enumitem package. If a new list environment is defined using \newlist
from enumitem
then randomList
will use this environment if you use the optional argument environment=<listname>
. (In my real use-case I want to use a custom enumitem
list environment, so this was important for me.) Ultimately, the list elements are printed inside an enumerate, itemize, description, ... environment so anything that works for these environments should work for randomList
.
The optional arguments for randomList
are processed using pgfkeys. They can be accessed directly using \pgfkeys{/randomList, ...}
.
Final remark: I thought that this would be quite slow but I am using essentially this code to produce three randomly ordered versions of a list of 20 questions, together with some other stuff, and it's really quick!
-
This is great, thanks a lot! Is there any way to avoid having to add a blank line after every
\item
?– agubeluOct 8, 2019 at 12:33 -
@agubelu I now know how to rewrite this so as to avoid the blank lines but given Ignasi's answer perhaps there is no need for this anymore?– user30471Oct 9, 2019 at 13:32
As the usual implementation of \begin{list} \item ... \item ... \item ... \end{list}
doesn't read or save the actual text of the items, some out-of-my-league hacking needs to be done to save then permute list items if you want the same interface.
I asked a similar question on the pgf-users mailing list several years ago. Here is Mark Wibrow's answer for randomizing a PGF list of the form declared by \pgfmathdeclarelist
. I've dropped this snippet into my exam files ever since.
\documentclass{article}
\usepackage{tikz}
\begin{document}
\makeatletter
\def\pgfmathdeclarelist#1#2{%
\def\pgfmath@list@name{#1}%
\c@pgfmath@counta=0%
\pgfmath@declarelistlist#2{\pgfmath@stop}%
}%
\def\pgfmath@declarelistlist#1{%
\ifx#1\pgfmath@stop%
\expandafter\edef\csname pgfmath@list@\pgfmath@list@name
@length\endcsname{\the\c@pgfmath@counta}%
\else%
\advance\c@pgfmath@counta by1\relax%
\pgfutil@namedef{pgfmath@list@\pgfmath@list@name @\the\c@pgfmath@counta}{#1}%
\expandafter\pgfmath@declarelistlist%
\fi%
}
\def\pgfmathgetlistitem#1#2#3{\expandafter\let\expandafter#1\expandafter=\csname
pgfmath@list@#2@#3\endcsname}
\def\pgfmathsetlistitem#1#2#3{%
\pgfutil@namedef{pgfmath@list@#1@#2}{#3}%
}
\def\pgfmathgetlistlength#1#2{%
\expandafter\let\expandafter#1\expandafter=\csname
pgfmath@list@#2@length\endcsname%
}
\def\pgfmathknuthshuffle#1{%
\pgfmathgetlistlength\pgfmath@len{#1}%
\pgfmathloop%
\ifnum\pgfmathcounter>\pgfmath@len%
\else%
\pgfmathrandominteger\pgfmath@temp{1}{\pgfmath@len}%
\pgfmathgetlistitem\pgfmath@@temp{#1}{\pgfmathcounter}%
\pgfmathgetlistitem\pgfmath@@@temp{#1}{\pgfmath@temp}%
\def\pgfmath@marshal{\pgfmathsetlistitem{#1}}%
\expandafter\pgfmath@marshal\expandafter{\expandafter\pgfmath@temp\expandafter}\expandafter{\pgfmath@@temp}%
\expandafter\pgfmath@marshal\expandafter{\expandafter\pgfmathcounter\expandafter}\expandafter{\pgfmath@@@temp}%
\repeatpgfmathloop%
}
\makeatother
\pgfmathdeclarelist{mylist}{{A}{B}{C}{D}{E}{F}{G}{H}{I}{J}{K}{L}{M}}
\pgfmathgetlistlength{\l}{mylist}
\begin{tikzpicture}[every node/.style={circle, draw}]
\foreach \i in {1,...,\l}{
\pgfmathgetlistitem{\x}{mylist}{\i}
\node at (0,-\i) (\x-1) {\x};
}
\pgfmathknuthshuffle{mylist}
\foreach \i in {1,...,\l}{
\pgfmathgetlistitem{\x}{mylist}{\i}
\node at (7.5,-\i) (\x-2) {\x};
}
\foreach \i in {1,...,\l}{
\pgfmathgetlistitem{\x}{mylist}{\i}
\draw [->] (\x-1) -- (\x-2);
}
\end{tikzpicture}
\end{document}
Today (quite late for OP) I've found randomlist
package which among other possibilities offers \RandomItemizeList
and \RandomEnumerateList
commands. These commands produce what they say, a random list of items with or without numbering them.
A little example:
\documentclass{article}
\usepackage{randomlist}
\begin{document}
This is a random list of items:
\RandomItemizeList{First item}{Second item}{Third item}{Fourth item}
and this is an enumerated random list of items:
\RandomEnumerateList{First item}{Second item}{Third item}{Fourth item}
\end{document}
After first compilation, it produces:
a second compilation gives:
and so on.