4

If I have an itemize environment with 5 items and I would like to have the following effect:

Every time I compile, exactly 3 random non-repeating items are listed in a random order.

Say my items are

  • A
  • B
  • C
  • D
  • E

And after one compiling, the output may look like:

  • D
  • A
  • E

My question is closed related to, yet different from this one. One application of this is setting examination papers from a given collection of problems.

Anyone knows if it is possible with latex? Thank you!

2
  • Why not use one of the pakages dedicated to exam preparation, such as xsim?
    – leandriis
    Dec 30 '19 at 20:18
  • @leandriis, I am writing a study guide consists of questions using enumerate. Then I would like to find a quick way to generate an exam from this list by randomly choosing some questions.
    – Zuriel
    Dec 30 '19 at 20:50
3

You can use expl3. With the b argument type the entire environment's contents is absorbed.

We can split it at \item (discarding the first empty item in the so-obtained sequence). Then we shuffle the sequence and output only the first three items, or how many are specified in the optional argument. If we request more items than are available, no problem.

The \seq_indexed_map_inline:Nn function is very handy in this respect: it produces both an integer ##1 and the corresponding item ##2 so we can test whether the index is greater than either the number of desired items or the length of the sequence.

The idea is similar to Andrew's, but the implementation shows different features and is simpler.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentEnvironment{randomlist}{ O{3} +b }
 {
  \seq_set_split:Nnn \l__zuriel_randomlist_seq { \item } { #2 }
  \seq_pop_left:NN \l__zuriel_randomlist_seq \l_tmpa_tl
  \seq_shuffle:N \l__zuriel_randomlist_seq
  \begin{itemize}
  \seq_indexed_map_inline:Nn \l__zuriel_randomlist_seq
   {
    \int_compare:nTF { ##1 > \int_min:nn { #1 } { \seq_count:N \l__zuriel_randomlist_seq } }
     {
      \seq_map_break:
     }
     {
      \item ##2
     }
   }
  \end{itemize}
 }{}

\seq_new:N \l__zuriel_randomlist_seq

\ExplSyntaxOff

\begin{document}

This prints three items (default)
\begin{randomlist}
  \item A
  \item B
  \item C
  \item D
  \item E
\end{randomlist}

This prints two items
\begin{randomlist}[2]
  \item A
  \item B
  \item C
  \item D
  \item E
\end{randomlist}

This prints five items
\begin{randomlist}[5]
  \item A
  \item B
  \item C
  \item D
  \item E
\end{randomlist}

This prints five items as well
\begin{randomlist}[10]
  \item A
  \item B
  \item C
  \item D
  \item E
\end{randomlist}

\end{document}

enter image description here

2
  • 1
    I find this a little bizarre. You answer 11 hours after me using essentially the same idea, which you don't acknowledge, and then people up-vote you and not me.
    – user30471
    Mar 18 '20 at 2:38
  • @Andrew You're right, I forgot to mention.
    – egreg
    Mar 18 '20 at 9:41
2

The "new" LaTeX3 coding interface is perfect for questions like this. Using it you can split the contents of a list environment using the \item commands to create a sequence, you can then use \seq_shuffle:N to randomly shuffle the sequence, which then allows you to print the list items in random order. The code is short and, once you are comfortable with LaTeX 3, it is quite easy to understand.

Implementing these ideas, the code below defines a randonlist environment that takes an optional argument, which specifies how many of the items to print -- by default all of the list items will appear but in a random order. The following code then answers the question in the OP:

Three random items
\begin{randomlist}[3]
  \item A
  \item B
  \item C
  \item D
  \item E
\end{randomlist}

Here is the example output that I generated the first time I ran the code (every time the file is compiled the output changes):

enter image description here

If you omit the optional argument then the entire list is shuffled:

enter image description here

Here is the code:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\seq_new:N \l_randomlist_seq
\int_new:N \l_random_items_int
\NewDocumentEnvironment{randomlist}{ o  +b }
{
    % use a regular expression to split the environment
    % around the \item commands
    \regex_split:nnN { \s*\c{item}\s* } { #2 } \l_randomlist_seq
    \seq_pop_left:NN \l_randomlist_seq \l_items_tl % pop empty first item
    \seq_if_empty:NF \l_randomlist_seq {
        \seq_shuffle:N \l_randomlist_seq %% randonly shuffle items
        % the number of items to print is given by the optional #1
        % defaulting to the full list of items
        \IfNoValueTF{#1}{
            \int_set:Nn \l_random_items_int { \seq_count:N \l_randomlist_seq}
        }{
            \int_set:Nn \l_random_items_int { #1 }
        }
        % finally, insert the itemize environment
        \begin{itemize}
          \int_do_while:nNnn \l_random_items_int > 0 {
            \seq_pop:NN \l_randomlist_seq \l_tmpa_tl
            \item \tl_use:N \l_tmpa_tl
            \int_decr:N \l_random_items_int% one less item to go
          }
        \end{itemize}
    }
}{}
\ExplSyntaxOff


\begin{document}

  Five random items
  \begin{randomlist}
    \item A
    \item B
    \item C
    \item D
    \item E
  \end{randomlist}

  Three random items
  \begin{randomlist}[3]
    \item A
    \item B
    \item C
    \item D
    \item E
  \end{randomlist}


  Two random items
  \begin{randomlist}[2]
    \item A
    \item B
    \item C
    \item D
    \item E
  \end{randomlist}

\end{document}

Personally, I would use an enumerate environment for this but I have used an itemize environment as in the OP.

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.