2

Consider the following code. Is such a randomization possible? I have many different tables where the number of letters may vary from table to table. However, within each table, the letters correspond always to the first letters of the alphabet.

enter image description here

\documentclass[english]{article}
\usepackage[T1]{fontenc}
\usepackage[latin9]{inputenc}

\makeatletter

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% LyX specific LaTeX commands.
%% Because html converters don't know tabularnewline
\providecommand{\tabularnewline}{\\}

\makeatother

\usepackage{babel}
\begin{document}
\begin{tabular}{|c|c|c|}
\hline 
\fbox{A} & 4 & \fbox{C}\tabularnewline
\hline 
2 & \fbox{B} & 3\tabularnewline
\hline 
\end{tabular}

\bigskip{}

\fbox{A} 5 \fbox{B} 6 \fbox{C} 7

\bigskip{}

I want to randomize A, B and C in different versions of this document.
For instance, one possible randomization is the following:

\bigskip{}

\begin{tabular}{|c|c|c|}
\hline 
\fbox{B} & 4 & \fbox{A}\tabularnewline
\hline 
2 & \fbox{C} & 3\tabularnewline
\hline 
\end{tabular}

\bigskip{}

\fbox{A} 7 \fbox{B} 5 \fbox{C} 6 (Please, notice that now A, B
and C correspond to 7, 5 and 6.)

\bigskip{}

\end{document}
PaulS
  • 461
  • So the numbers should always be at the same spot but the three letters should be randomly placed in the remaining spots? Is there any rule to the numbers placement if you have more than three letters? Can you please explain the logic behind how these tables built? – Skillmon Jun 09 '19 at 13:52
  • @Skillmon Thanks! The letters A, B, C, D, ... hide the entries of the table where they are placed. So, when I randomize the letters, I am only randomize the letters and not the numbers corresponding to the entries where the letters are placed. Thus, in the example I used, A = 5, B = 6 and C = 7, after randomization, becomes A = 7, B = 5 and C = 6. That is, the entries of the tables do not change; only their labels change by randomization. – PaulS Jun 09 '19 at 14:07
  • 1
    Somewhat related: https://tex.stackexchange.com/questions/346260/display-item-list-in-sequential-and-random-without-repetition-order/346285?r=SearchResults&s=1|32.3547#346285. Basically you can randomize the order of anything. – John Kormylo Jun 09 '19 at 16:11
  • 1
    Also related: https://tex.stackexchange.com/questions/63357/automatically-generated-bingo-cards – Scott H. Jun 09 '19 at 23:23

2 Answers2

2

I hope the following does what you want. It creates an environment called randomhidetable that is a tabular in which each field with \hide is replaced by a random element of the sequence of first $n$ characters of the alphabet (each element is only used once). The table is evaluated twice, the first evaluation counts the number of \hide elements, the second typesets it. You can change \hide to any arbitrary macro using the second optional argument of the environment (that follows the mandatory argument).

The formatting of the hide labels can be customized by changing the definition of \__paul_hide_output_format:n (that currently is defined as \fbox{#1}).

You can output the hidden cells using the macro \hiddencells which takes four arguments:

  1. separator between label and value
  2. separator between the different labels if there are only two
  3. separator between the different labels if there are more than two (except the final two)
  4. separator between the final two labels if there are more than two
\documentclass[]{article}

\usepackage{xparse}
\ExplSyntaxOn
\tl_new:N \l__paul_reveal_separator_tl
\int_new:N \g__paul_hide_count_int
\seq_new:N \g__paul_hide_seq
\seq_new:N \g__paul_hidden_seq
\seq_new:N \g__paul_reveal_seq
\NewDocumentEnvironment { randomhidetable } { O{c} +m O{\hide} +b }
  {
    \group_begin:
      \int_gzero:N \g__paul_hide_count_int
      \cs_set:Npn #3 ##1 { \int_gincr:N \g__paul_hide_count_int }
      \hbox_set:Nn \l_tmpa_box { \begin { tabular } { #2 } #4 \end { tabular } }
    \group_end:
    \seq_gclear:N \g__paul_hide_seq
    \seq_gclear:N \g__paul_hidden_seq
    \int_step_inline:nn \g__paul_hide_count_int { \seq_gpush:Nn \g__paul_hide_seq { ##1 } }
    \seq_gshuffle:N \g__paul_hide_seq
    \cs_set:Npn #3 ##1
      {
        \seq_gpop:NN \g__paul_hide_seq \l_tmpa_tl
        \__paul_hide_output_format:x { \char_generate:nn { 64 + \l_tmpa_tl } { 11 } }
        \seq_gpush:Nx \g__paul_hidden_seq { { \l_tmpa_tl } { ##1 } }
      }
    \begin { tabular } [ #1 ] { #2 } #4 \end { tabular }
    \seq_gsort:Nn \g__paul_hidden_seq
      {
        \int_compare:nNnTF { \use_i:nn ##1 } > { \use_i:nn ##2 }
          { \sort_return_swapped: }
          { \sort_return_same: }
      }
    \seq_gclear:N \g__paul_reveal_seq
    \seq_map_inline:Nn \g__paul_hidden_seq
      {
        \__paul_hide_output_hidden_values:nn ##1
      }
  }
  {}
\cs_new:Npn \__paul_hide_output_hidden_values:nn #1 #2
  {
    \seq_gput_right:Nx \g__paul_reveal_seq
      {
        \exp_not:N \__paul_hide_output_format:n
          { \char_generate:nn { 64 + #1 } { 11 } }
        \exp_not:n { \l__paul_reveal_separator_tl #2 }
      }
  }
\cs_new:Npn \__paul_hide_output_format:n #1
  {
    \fbox { #1 }
  }
\cs_generate_variant:Nn \__paul_hide_output_format:n { x }
\NewDocumentCommand \hiddencells { m m m m }
  {
    \group_begin:
      \tl_set:Nn \l__paul_reveal_separator_tl { #1 }
      \seq_use:Nnnn \g__paul_reveal_seq { #2 } { #3 } { #4 }
    \group_end:
  }
\ExplSyntaxOff

\begin{document}
\makebox[2cm][l]{First call:}
\begin{randomhidetable}[b]{ccc}
  \hide{5} & 4 & \hide{7} \\
  2 & \hide{6} & 3
\end{randomhidetable}
The hidden fields were: \hiddencells{~}{, }{, }{, and }

\makebox[2cm][l]{Second call:}
\begin{randomhidetable}[t]{ccc}[\foo]
  \foo{5} & 4 & \foo{7} \\
  2 & \foo{6} & 3
\end{randomhidetable}
The hidden fields were: \hiddencells{~}{ and }{, }{, and }
\end{document}

enter image description here

EDIT: a version that uses the environ package as an alternative if you don't have a recent enough version of xparse. The environ based environment is called randomhidetable* and doesn't support the second optional argument. To still be able to set the used macro the new macro \sethidemacro is introduced (the old environment still works):

\documentclass[]{article}

\usepackage{xparse}
\usepackage{environ}
\ExplSyntaxOn
\tl_new:N \l__paul_reveal_separator_tl
\tl_new:N \l__paul_hide_macro_name_tl
\tl_set:Nn \l__paul_hide_macro_name_tl { \hide }
\int_new:N \g__paul_hide_count_int
\seq_new:N \g__paul_hide_seq
\seq_new:N \g__paul_hidden_seq
\seq_new:N \g__paul_reveal_seq
%\NewDocumentEnvironment { randomhidetable } { O{c} +m o +b }
%  {
%    \IfValueTF { #3 }
%      { \__paul_randomhidetable_code:nnnn { #1 } { #2 } { #3 } { #4 } }
%      {
%        \__paul_randomhidetable_code:nnVn
%          { #1 } { #2 } \l__paul_hide_macro_name_tl { #4 }
%      }
%  }
%  {}
\cs_new:Npn \__paul_randomhidetable_code:nnnn #1 #2 #3 #4
  {
    \group_begin:
      \int_gzero:N \g__paul_hide_count_int
      \cs_set:Npn #3 ##1 { \int_gincr:N \g__paul_hide_count_int }
      \hbox_set:Nn \l_tmpa_box { \begin { tabular } { #2 } #4 \end { tabular } }
    \group_end:
    \seq_gclear:N \g__paul_hide_seq
    \seq_gclear:N \g__paul_hidden_seq
    \int_step_inline:nn \g__paul_hide_count_int { \seq_gpush:Nn \g__paul_hide_seq { ##1 } }
    \seq_gshuffle:N \g__paul_hide_seq
    \cs_set:Npn #3 ##1
      {
        \seq_gpop:NN \g__paul_hide_seq \l_tmpa_tl
        \__paul_hide_output_format:x { \char_generate:nn { 64 + \l_tmpa_tl } { 11 } }
        \seq_gpush:Nx \g__paul_hidden_seq { { \l_tmpa_tl } { ##1 } }
      }
    \begin { tabular } [ #1 ] { #2 } #4 \end { tabular }
    \seq_gsort:Nn \g__paul_hidden_seq
      {
        \int_compare:nNnTF { \use_i:nn ##1 } > { \use_i:nn ##2 }
          { \sort_return_swapped: }
          { \sort_return_same: }
      }
    \seq_gclear:N \g__paul_reveal_seq
    \seq_map_inline:Nn \g__paul_hidden_seq
      {
        \__paul_hide_output_hidden_values:nn ##1
      }
  }
\cs_generate_variant:Nn \__paul_randomhidetable_code:nnnn { nnVV }
\cs_generate_variant:Nn \__paul_randomhidetable_code:nnnn { nnVn }
\cs_new:Npn \__paul_hide_output_hidden_values:nn #1 #2
  {
    \seq_gput_right:Nx \g__paul_reveal_seq
      {
        \exp_not:N \__paul_hide_output_format:n
          { \char_generate:nn { 64 + #1 } { 11 } }
        \exp_not:n { \l__paul_reveal_separator_tl #2 }
      }
  }
\cs_new:Npn \__paul_hide_output_format:n #1
  {
    \fbox { #1 }
  }
\cs_generate_variant:Nn \__paul_hide_output_format:n { x }
\NewDocumentCommand \hiddencells { m m m m }
  {
    \group_begin:
      \tl_set:Nn \l__paul_reveal_separator_tl { #1 }
      \seq_use:Nnnn \g__paul_reveal_seq { #2 } { #3 } { #4 }
    \group_end:
  }
\NewDocumentCommand \sethidemacro { m }
  {
    \tl_set:Nn \l__paul_hide_macro_name_tl { #1 }
  }
\NewEnviron{randomhidetable*}[2][c]
  {
    \__paul_randomhidetable_code:nnVV
      { #1 } { #2 } \l__paul_hide_macro_name_tl \BODY
  }
\ExplSyntaxOff


\begin{document}
\makebox[2cm][l]{First call:}
\begin{randomhidetable*}[b]{ccc}
  \hide{5} & 4 & \hide{7} \\
  2 & \hide{6} & 3
\end{randomhidetable*}
The hidden fields were: \hiddencells{~}{ and }{, }{, and }

\makebox[2cm][l]{Second call:}
\begingroup
\sethidemacro{\foo}
\begin{randomhidetable*}[t]{ccc}
  \foo{5} & 4 & \foo{7} \\
  2 & \foo{6} & 3
\end{randomhidetable*}
\endgroup
The hidden fields were: \hiddencells{~}{ and }{, }{, and }
\end{document}
Skillmon
  • 60,462
  • Thanks! Is it possible to write the values of A, B and C of each table in one line, as I do in my example, sorting the letters by alphabetic order? – PaulS Jun 09 '19 at 15:06
  • @PaulSmith sure, give me a minute – Skillmon Jun 09 '19 at 15:52
  • @PaulSmith took a bit more than a minute (there was dinner in the way), but posted an edit, you should discard everything from this answer before the edit and copy the entire code. – Skillmon Jun 09 '19 at 16:50
  • Thanks again! I am getting an error while compiling your code: " Unknown argument type 'b' for the command '\environment randomhidetable'" (line 40). – PaulS Jun 09 '19 at 18:30
  • @PaulSmith in that case, your LaTeX installation is out of date and you should update it. – Skillmon Jun 09 '19 at 18:39
  • @PaulSmith if for some reason you can't update your installation, I provided a second version that uses \NewEnviron from the environ package instead. – Skillmon Jun 09 '19 at 18:51
  • Thanks, but still getting errors. There is TexLive 2019, but Fedora linux still uses TexLive 2018 -- that is why I do not have the last version of xparser. – PaulS Jun 09 '19 at 19:22
  • @PaulSmith the starred version still doesn't work? What is the error message you get? Did you copy my entire code (that is important, the starred version needs changes of the other version to function)? – Skillmon Jun 09 '19 at 22:04
  • It does not. But I have found meanwhile a simple solution for my own question, by using R via knitr. I am going to post my solution. – PaulS Jun 09 '19 at 22:37
  • 1
    @PaulSmith comment out the \NewDocumentEnvironment { randomhidetable } block. – Skillmon Jun 10 '19 at 08:26
  • Now, it works! Thanks! – PaulS Jun 10 '19 at 09:11
1

Since Skillmon's solution does not compile on my LaTeX distribution (Fedora Linux still uses TexLive 2018), I had to look for another solution. And I have found the following one, which uses R via knitr.

enter image description here

\documentclass[english]{article}
\usepackage[T1]{fontenc}
\usepackage[latin9]{inputenc}
\usepackage{geometry}
\geometry{verbose,tmargin=2cm,bmargin=2cm,lmargin=2cm,rmargin=2cm}

\makeatletter

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% LyX specific LaTeX commands.
%% Because html converters don't know tabularnewline
\providecommand{\tabularnewline}{\\}

\makeatother

\usepackage{babel}
\begin{document}
<<echo=F>>=

v <- c("A","B","C")
w <- c(10,20,30)
order <- sample(1:3)
v <- v[order]
@

\noindent \begin{center}
\begin{tabular}{|c|c|c|}
\hline 
\fbox{\Sexpr{v[1]}} & 4 & \fbox{\Sexpr{v[3]}}\tabularnewline
\hline 
2 & \fbox{\Sexpr{v[2]}} & 6\tabularnewline
\hline 
\end{tabular}
\par\end{center}

This time, we have: A=\Sexpr{w[which(v == "A")]}, B=\Sexpr{w[which(v == "B")]}
e C=\Sexpr{w[which(v == "C")]}.
<<echo=F>>=

ordem <- sample(1:3)
v <- v[ordem]
@

\noindent \begin{center}
\begin{tabular}{|c|c|c|}
\hline 
\fbox{\Sexpr{v[1]}} & 4 & \fbox{\Sexpr{v[3]}}\tabularnewline
\hline 
2 & \fbox{\Sexpr{v[2]}} & 6\tabularnewline
\hline 
\end{tabular}
\par\end{center}

This time, we have: A=\Sexpr{w[which(v == "A")]}, B=\Sexpr{w[which(v == "B")]}
e C=\Sexpr{w[which(v == "C")]}.
\end{document}
PaulS
  • 461