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.

The etoolbox package provides macros to handle list, which can be useful to package/class writers, however it miss macros to get random sublists.

So I want to define the following macros:

\getrandomsublist{<to store list macro>}{<sublist length>}{<list macro>}
\getrandomsublistcs{<to store list macro>}{<sublist length>}{<list name>}
\getcsrandomsublist{<to store list name>}{<sublist length>}{<list macro>}
\getcsrandomsublistcs{<to store list name>}{<sublist length>}{<list name>}

To start with, I have attempted the following:

\documentclass{article}

\usepackage{etoolbox}
\usepackage{pgfcore}
\usepackage{ifthen}
\usepackage{forloop}

%% this stores the length of a given list in the a counter, for later use
\newcounter{thelistlength}
\newcommand{\listlength}[1]{%
    \setcounter{thelistlength}{0}%
    \renewcommand{\do}{%
        \stepcounter{thelistlength}%
    }%
    \dolistcsloop{#1}%
}

%% for test it with csv lists
\newcommand{\myinternallist}{}
\newcommand{\tomyinternallist}[1]{%
    \forcsvlist{\listadd\myinternallist}{#1}%
}

%% \getrandomsublist{<sublist length>}{<list name>}
%% store the resulting sublist in a `\outputlist`
\newcommand{\outputlist}{}%
\newcommand{\getrandomsublist}[2]{%
    \listlength{#2}%                              % calculate the length of the list 
    \newcommand{\randomindex}%                    
    \newcounter{innerct}%
    \setcounter{innerct}{0}%
    \renewcommand{\do}[1]{%
        \stepcounter{innerct}%
        \ifnum \randomindex=\value{innerct}%
            \listgadd{\outputlist}{##1}%
        \fi%
    }
    \newcounter{ct}\forloop{ct}{0}{\value{ct}<#1}{%
        \pgfmathrandominteger{\randomindex}{\value{thelistlength}}{1}%
        \docslistloop{#2}%
    }%
    \outputlist
}

\begin{document}
\tomyinternallist{really,random,list,of,items}
\listlength{myinternallist}
\thethelistlength
\getrandomsublist{2}{myinternallist}
\renewcommand{\do}[1]{#1\qquad}
\dolistloop{\outputlist}
\end{document}

without success.

How can we effectively define these macros?

share|improve this question
    
Is it lualatex a valid option? –  JLDiaz Mar 5 '13 at 17:45
    
@JLDiaz I'll prefer something that allow me to keep manipulating lists with etoolbox. But I'm interested in your approach –  leo Mar 5 '13 at 18:06
add comment

1 Answer 1

up vote 5 down vote accepted

I wouldn't use comma separated lists, that are handy for input, but a nuisance for manipulation.

Here's a LaTeX3 implementation, where each list has a name; it's inspired by my answer here.

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

\ExplSyntaxOn
\NewDocumentCommand{\definelist}{mm}
 {
  \leo_define_list:nn {#1} {#2}
 }
\cs_new_protected:Npn \leo_define_list:nn #1 #2
 {
  \seq_gset_split:cnn { g_leo_list_ #1 _seq } { , } { #2 }
 }
\cs_generate_variant:Nn \seq_gset_split:Nnn { c }
\NewDocumentCommand{\getrandomsublist}{mmm}
 {
  \leo_get_random_sublist:nNN {#1} {#2} {#3}
 }

\cs_new_protected:Npn \leo_get_random_sublist:nNN #1 #2 #3
 {
  \seq_clear:N \l_leo_used_seq
  \seq_gclear_new:c { g_leo_list_#2_seq }
  \int_set:Nn \l_leo_length_int { \seq_count:c { g_leo_list_#3_seq } }
  \int_compare:nTF { #1 > \l_leo_length_int }
   {
    \msg_error:nnxx { randomchoice } { too-many } { #1 } { \int_to_arabic:n { \l_leo_length_int } }
   }
   {
    \int_step_inline:nnnn { 1 } { 1 } { #1 }
     {
      \leo_get_random:
      \seq_gput_right:cx { g_leo_list_#2_seq}
       { \seq_item:cn { g_leo_list_#3_seq } { \l_leo_random_int } }
     }
   }
 }
\cs_new_protected:Npn \leo_get_random:
 {
  \setrannum { \l_leo_random_int } { 1 } { \l_leo_length_int }
  \seq_if_in:NxTF \l_leo_used_seq { \int_to_arabic:n { \l_leo_random_int } }
   { \leo_get_random: }
   { \seq_put_right:Nx \l_leo_used_seq { \int_to_arabic:n { \l_leo_random_int } } }
 }
\seq_new:N \l_leo_used_seq
\clist_new:N \l_leo_temp_clist
\int_new:N \l_leo_length_int
\int_new:N \l_leo_random_int
\msg_new:nnnn { randomchoice } { too-many }
 { Too~ many~choices }
 { You~want~to~select~#1~elements,~but~you~have~only~#2 }

%%% Printing the new list 
\NewDocumentCommand{\printlist}{m}
 {
  \seq_use:cnnn { g_leo_list_#1_seq } {,} {,} {,}
 }
\cs_generate_variant:Nn \seq_use:Nnnn { c }
\ExplSyntaxOff

\begin{document}
\definelist{myinternallist}{really,random,list,of,items,with,many,objects,in,it}
\getrandomsublist{2}{randomlisti}{myinternallist}
\getrandomsublist{3}{randomlistii}{myinternallist}
\getrandomsublist{4}{randomlistiii}{myinternallist}
\getrandomsublist{5}{randomlistiv}{myinternallist}
\getrandomsublist{10}{randomlistv}{myinternallist}

\ttfamily
\printlist{randomlisti}

\printlist{randomlistii}

\printlist{randomlistiii}

\printlist{randomlistiv}

\printlist{randomlistv}

\end{document}

Of course you can use the sequences in a different way, maybe for getting items from a database. If, say, \getitem is the macro so that \getitem{foo} uses item foo from the database, you can define

\NewDocumentCommand{\uselist}{m}
 {
  \seq_map_inline:cn { g_leo_list_#1_seq } { \getitem{##1} }
 }

in the preamble and say

\getrandomsublist{2}{randomlisti}{myinternallist}
\uselist{randomlisti}

because \seq_map_inline:cn will traverse the newly built sequence one item at a time.

share|improve this answer
    
The comma-separated list are for test purposes only. Thanks for your answer –  leo Mar 5 '13 at 18:10
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.