1

I got the following code from Random shuffle itemize.

How to label (something like \label{1}, \ref{1}) a specific randomList item to print the corresponding letter at the end of the document?

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


  \begin{randomList}[environment=Testlist, seed=4]
    \item First item

    \item Second item

    \item Third item

  \end{randomList}


\end{document}
2

Using a fixed seed is mandatory, otherwise the reference will change from one run to the other.

\documentclass{article}
\usepackage{xparse,environ,enumitem}

\ExplSyntaxOn
\NewEnviron{randomList}[1][]
 {
  \keys_set:nn { lucas/randomlist } { #1 }
  \seq_set_split:NnV \l__lucas_randomlist_items_seq { \item } \BODY
  % discard the empty first item
  \seq_pop_left:NN \l__lucas_randomlist_items_seq \l_tmpa_tl
  \seq_shuffle:N \l__lucas_randomlist_items_seq
  \begin{\l__lucas_randomlist_env_tl}
  \item \seq_use:Nn \l__lucas_randomlist_items_seq { \item }
  \end{\l__lucas_randomlist_env_tl}
 }
\cs_generate_variant:Nn \seq_set_split:Nnn { NnV }
\keys_define:nn { lucas/randomlist }
 {
  environment .tl_set:N = \l__lucas_randomlist_env_tl,
  seed .code:n = \sys_gset_rand_seed:n { #1 },
 }
\ExplSyntaxOff

\newlist{Testlist}{enumerate}{1} %
\setlist[Testlist]{label*=\alph*.}
\setlist{nosep}\parindent=0pt% for more compact output

\begin{document}


\begin{randomList}[environment=Testlist, seed=4]
\item\label{1} First item

\item Second item

\item Third item
\end{randomList}

\ref{1}

\end{document}

enter image description here

You can also make an answer key, provided it comes later than the exercises.

\documentclass{article}
\usepackage{xparse,environ,enumitem}

\ExplSyntaxOn
\NewEnviron{randomList}[1][]
 {
  \keys_set:nn { lucas/randomlist } { #1 }
  \lucas_randomlist:V \BODY
 }
\NewDocumentCommand{\answerkey}{}
 {
  \subsubsection*{Answer ~ key}
  \begin{enumerate}[nosep]
  \seq_map_inline:Nn \g_lucas_randomlist_answers_seq { \item ##1 }
  \end{enumerate}
 }
\NewDocumentCommand{\correct}{}
 {
  \seq_gput_right:Nx \g_lucas_randomlist_answers_seq { \use:c { @currentlabel } }
 }

\keys_define:nn { lucas/randomlist }
 {
  environment .tl_set:N = \l__lucas_randomlist_env_tl,
  seed .code:n = \sys_gset_rand_seed:n { #1 },
 }

\seq_new:N \g_lucas_randomlist_answers_seq
\cs_new_protected:Nn \lucas_randomlist:n
 {
  \seq_set_split:Nnn \l__lucas_randomlist_items_seq { \item } { #1 }
  % discard the empty first item
  \seq_pop_left:NN \l__lucas_randomlist_items_seq \l_tmpa_tl
  \seq_shuffle:N \l__lucas_randomlist_items_seq
  \begin{\l__lucas_randomlist_env_tl}
  \item \seq_use:Nn \l__lucas_randomlist_items_seq { \item }
  \end{\l__lucas_randomlist_env_tl}
 }
\cs_generate_variant:Nn \lucas_randomlist:n { V }

\ExplSyntaxOff

\newlist{Testlist}{enumerate}{1}
\setlist[Testlist]{label*=\alph*.}
\setlist{nosep}\parindent=0pt% for more compact output

\begin{document}

\begin{enumerate} % outer list of exercises

\item First exercise
  \begin{randomList}[environment=Testlist]
  \item\correct First item

  \item Second item

  \item Third item
  \end{randomList}

\item Second exercise
  \begin{randomList}[environment=Testlist]
  \item First item

  \item\correct Second item

  \item Third item
  \end{randomList}

\item Third exercise
  \begin{randomList}[environment=Testlist]
  \item\correct First item

  \item Second item

  \item Third item
  \end{randomList}

\end{enumerate}

\answerkey

\end{document}

enter image description here

9
  • Dear @egreg , from your suggestion I'm getting the following error: ! Undefined control sequence. \key code > lucas/randomlist/seed ..._rand_seed:n {#1} l.37 \end{randomList} The control sequence at the end of the top line of your error message was never \def'ed. If you have misspelled it (e.g., \hobx'), type I' and the correct spelling (e.g., `I\hbox'). Otherwise just continue, and I'll forget about whatever was undefined. etc.
    – FrozenCore
    Dec 23 '18 at 11:59
  • 1
    @LucasMartins You need to update your TeX system, in particular the expl3 bundle.
    – egreg
    Dec 23 '18 at 12:01
  • Now, it's working perfectly . Thanks @egreg , for your answer, but I was looking for a solution that did not require a fixed seed. Is there another way to print the letter corresponding to a particular item?
    – FrozenCore
    Dec 23 '18 at 13:09
  • @LucasMartins You can add \nofiles to the preamble before running LaTeX a final time.
    – egreg
    Dec 23 '18 at 13:17
  • Thank you for your patience, @egreg , but your last suggestion did not work for me. Maybe I did not understand exactly what you meant by "before running LaTeX a final time".
    – FrozenCore
    Dec 23 '18 at 13:25

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.