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}

1 Answers1

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

egreg
  • 1,121,712
  • 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'), typeI' 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
  • could you edit your first reply? – FrozenCore Dec 23 '18 at 13:31
  • @LucasMartins Sorry, I realized that it can't work with \nofiles. Do you plan to use \ref only after the shuffled list? – egreg Dec 23 '18 at 14:22
  • I plan use \ref at the end of the document, to make a Answer Key for a multiple-choice-Exam. – FrozenCore Dec 23 '18 at 14:30
  • @LucasMartins I added a possible solution – egreg Dec 23 '18 at 14:45