17

Is there an environment (or macro for itemize) that shuffles all items randomly each time the pdf is generated?

15

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:

enter image description here

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.

8

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):

enter image description here

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!

7

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} 
1

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:

enter image description here

a second compilation gives:

enter image description here

and so on.

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.