从给定列表中获取随机子列表

从给定列表中获取随机子列表

etoolbox包提供了处理列表的宏,这对包/类编写者很有用,但是它缺少获取随机子列表的宏。

所以我想定义以下宏:

\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>}

首先,我尝试了以下操作:

\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}

没有成功。

我们如何才能有效地定义这些宏?

答案1

我不会使用逗号分隔的列表,因为它们对于输入很方便,但是对于操作却很麻烦。

这是一个 LaTeX3 实现,其中每个列表都有一个名称;它的灵感来自我的答案在这里

\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}

当然,你可以用不同的方式使用序列,比如从数据库中获取项目。如果\getitem是宏,那么\getitem{foo}可以使用数据库中的项目foo,你可以定义

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

在序言中说

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

因为\seq_map_inline:cn将一次遍历新建的序列中的一个项目。

相关内容