Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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.

share|improve this question
add comment

2 Answers 2

up vote 15 down vote accepted

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

share|improve this answer
    
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 '12 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 '12 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 '12 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 '12 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 '12 at 21:02
show 5 more comments

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}
share|improve this answer
    
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 '12 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 '12 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 '12 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 '12 at 22:30
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.