19

I would like to have a command (either user-defined, or from a package, or whatever) that has the basic form:

\selectNrandom{N}{list, of, comma, separated, elements}{code to execute}

which will select N distinct, random elements from the csv list that follows, and then executes the code on those elements.

For example, this would select two elements and then typeset them with a large space between them (think math quiz):

\selectNrandom{2}{N, W, Z, Q, R, C}{%
    \mathbb{firstElement} \qquad  \mathbb{secondElement}
    }

I don't know what kind of MWE to post other than the above since I don't really have any idea of how to even begin writing a macro to do this.

Justification for Asking (i.e., I did do my homework!): I have been reading through sources such as various package documentations (etextools, probsoln, datatool, etc) as well as a few books (Joy of TeX, The Advanced TeXbook) and various websites. But I am still very new to the whole programming aspects of LaTex and Tex.

2 Answers 2

17

Here's a version with xparse and LaTeX3 code, with the help of the random.tex file by D. Arsenau

\documentclass{article}
\usepackage{xparse}
\input{random}

\ExplSyntaxOn
\NewDocumentCommand{\htguse}{ m }
 {
  \use:c { htg_arg_#1: }
 }
\NewDocumentCommand{\selectNrandom}{ m m m }
 {
  \htg_select_n_random:nnn { #1 } { #2 } { #3 }
 }

\cs_new_protected:Npn \htg_select_n_random:nnn #1 #2 #3
 {
  \seq_clear:N \l_htg_used_seq
  \int_set:Nn \l_htg_length_int { \clist_count:n { #2 } }
  \int_compare:nTF { #1 > \l_htg_length_int }
   {
    \msg_error:nnxx { randomchoice } { too-many } { #1 } { \int_to_arabic:n { \l_htg_length_int } }
   }
   {
    \int_step_inline:nnnn { 1 } { 1 } { #1 }
     {
      \htg_get_random:
      \cs_set:cpx { htg_arg_##1: }
       { \clist_item:nn { #2 } { \l_htg_random_int } }
     }
    #3
   }
 }
\cs_new_protected:Npn \htg_get_random:
 {
  \setrannum { \l_htg_random_int } { 1 } { \l_htg_length_int }
  \seq_if_in:NxTF \l_htg_used_seq { \int_to_arabic:n { \l_htg_random_int } }
   { \htg_get_random: }
   { \seq_put_right:Nx \l_htg_used_seq { \int_to_arabic:n { \l_htg_random_int } } }
 }
\seq_new:N \l_htg_used_seq
\int_new:N \l_htg_length_int
\int_new:N \l_htg_random_int
\msg_new:nnnn { randomchoice } { too-many }
 { Too~ many~choices }
 { You~want~to~select~#1~elements,~but~you~have~only~#2 }
\ExplSyntaxOff

\begin{document}

\selectNrandom{2}
  {N, W, Z, Q, R, C}
  {$\mathbf{\htguse{1}}$ and $\mathbf{\htguse{2}}$}

\selectNrandom{3}
  {A, B, C}
  {$\mathbf{\htguse{1}}$, $\mathbf{\htguse{2}}$ and $\mathbf{\htguse{3}}$}

\selectNrandom{3}
  {N, W}
  {$\mathbf{\htguse{1}}$, $\mathbf{\htguse{2}}$ and $\mathbf{\htguse{3}}$}

\end{document}

The macros take care to check that distinct elements are chosen by maintaining the list of already extracted elements and doing a new choice if a number is extracted again.

You refer to the first, second, and so on, element by \htguse{1}, \htguse{2} and so on.

The third call will raise an error:

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! randomchoice error: "too-many"
! 
! Too many choices
! 
! See the randomchoice documentation for further information.
! 
! For immediate help type H <return>.
!...............................................  

l.56 ...bf{\htguse{2}}$ and $\mathbf{\htguse{3}}$}

? h
|'''''''''''''''''''''''''''''''''''''''''''''''
| You want to select 3 elements, but you have only 2
|...............................................

enter image description here

With a recent expl3 kernel, \input{random} is not needed any longer with pdflatex or LuaLaTeX (it still is necessary for XeLaTeX). The line

\setrannum { \l_htg_random_int } { 1 } { \l_htg_length_int }

can be substituted with

\int_set:Nn \l_htg_random_int { \fp_eval:n { randint( \l_htg_length_int ) } }
10
  • Wouldn't it be better to make a copy of the list and delete each selected element from the list? It might take longer for long lists and small n's, but I guess it could be much faster in the opposite situation. (Much depends on the actual data structure, I guess, and me not being a computer scientist means I might be mistaken.)
    – mbork
    Dec 8, 2012 at 23:21
  • @mbork I think that removing an element from a list is slower than recomputing a random number, but I might be wrong. If it ain't broken, don't fix it. :) And the current macros of LaTeX3 don't allow for removing a specified element from a clist, so I'd have to implement it.
    – egreg
    Dec 8, 2012 at 23:25
  • Well, I'm not sure, but recomputing a random number a few times... Anyway, I'd guess that for quite a few applications, the list would be much longer than n anyway.
    – mbork
    Dec 9, 2012 at 0:48
  • 1
    @egreg I just copy/pasted your code into a new document and ran it. I'm getting an Undefined control sequence error. It is complaining about \clist_count:n.
    – HTG
    Dec 9, 2012 at 3:13
  • 1
    @HTG Yes, the code needs the last version of l3kernel and relative packages. Sorry if you had some inconveniences; but updating regularly is a good thing to do.
    – egreg
    Dec 9, 2012 at 21:02
8

Here's a Lua solution, the chosen arguments can be accessed as #1, #2, etc. As I haven't fully wrapped my head around the interaction between TeX and lua with respect to expansion, I can't say that I'm sure that using #1 etc. will behave in the same way as you might expect in a regular macro definition.

enter image description here

\documentclass{article}
\usepackage{luacode}

\begin{luacode*}
local rand = math.random
local args = {}
function getnrand(n,l,f)
    tab = string.explode(l,",")
    for i = 1,n do
        local num = rand(#tab)
        args[i] = tab[num]
        table.remove(tab,num)
    end
    f = string.gsub(f,"#(%d+)",
            function(n)
                return tostring(args[tonumber(n)])
            end)
    tex.sprint(f)
end
\end{luacode*}

\long\def\selectNrandom#1#2#3{\directlua{getnrand(#1,"\luatexluaescapestring{\unexpanded{#2}}","\luatexluaescapestring{\unexpanded{#3}}")}}

\begin{document}

\selectNrandom{3}{a,b,c,d,e,f,g}{
The first random letter is: \textbf{#1}

The second random letter is: \textbf{#2}

The third random letter is: \textbf{#3}
}

\end{document}
4
  • Does it work also with #10 and so on? I guess so, and it would be quite a practical way to use the results.
    – egreg
    Dec 8, 2012 at 23:56
  • @egreg Yes it will work with #n with n>9. The gsub part finds integers following a # and then replaces the #blah string with the appropriate argument. Since it wasn't too hard to maintain the # syntax, I also thought it would be sort of handy.
    – Scott H.
    Dec 9, 2012 at 0:21
  • @ScottH. Thanks for your answer and other help. I selected egreg's answer only because I didn't have to change how I compiled my files, whereas using LuaTeX would be a bigger change for me.
    – HTG
    Dec 12, 2012 at 22:15
  • No problem, I knew that @egreg would beat me to an xparse version, so I went with Lua. I'm glad that you got your question answered, and I learned something making the solution so everybody comes out ahead :)
    – Scott H.
    Dec 12, 2012 at 22:30

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.