2

What is the proper way to use loops inside the tabular environment?

The context of my question is generating tables using the datatool package. I need the following features:

  1. In each row, I want to generate the cells using a loop
  2. Inside each cell, I should be able to:
    • Be able to use the \multicolumn{1}{c} macro to change the column format
    • Use the value of the loop counter, for example inside if/then statements and to get certain datatool values

I was not able to work out how to do these three things. Following this entry, I have tried several approaches, which are summarised by:

\def\tand{&}
\newcounter{k}

% attepmt 1a
\def\firstattempta{
\setcounter{k}{1}
\whiledo{\thek < 3}{
    header\ \thek
    %\multicolumn{1}{c}{header \thek} % multicolumn not accepted
    \tand
    \stepcounter{k}
    }
header\ \thek
}

% attempt 1b
\makeatletter
  \def\xwhilenum #1\do{\@whilesw{\ifnum #1}\fi }
\makeatother

\def\firstattemptb{
\setcounter{k}{1}
\xwhilenum{\thek < 3}\do{%
    header\ \thek
    %\multicolumn{1}{c}{header \thek} % multicolumn not accepted
    \stepcounter{k}
    &    
    }
header\ \thek
}

% attempt 2
\setcounter{k}{1}
\def\secondattempt{}
    \whiledo{\thek < 3}{%
        \expandafter\def\expandafter\secondattempt\expandafter{%
        \secondattempt
        \multicolumn{1}{c|}{header \thek}
        \ifnum\thek<4 \tand \fi % 4 should be any other but it doesn't work
        }
        \stepcounter{k}%
        }
\expandafter\def\expandafter\secondattempt\expandafter{
    \secondattempt\multicolumn{1}{c|}{header \thek}} % (*)

\begin{document}

\begin{tabular}{*{3}{|D{.}{,}{-1}}|}
\hline
\firstattempta \\
\hline
\firstattemptb \\
\hline
\secondattempt \\
\hline
\end{tabular}

\end{document} 

enter image description here

3
  • The way to do this is to build up the complete row inside a loop and then use the stored row at once. (not sure how to describe this better)
    – Skillmon
    May 8, 2019 at 8:37
  • By the way: Welcome to TeX.SX!
    – Skillmon
    May 8, 2019 at 11:21
  • The \DTLforeachkeyinrow (datatool package) command doesn't allow \multicolumn inside neither.
    – e_moro
    May 12, 2019 at 11:55

1 Answer 1

5

The following is a loop over n columns. It has to be used as the first element of a row so you can only use it to loop over a whole row or the first n columns of a row. The first argument of \colloop is the number of columns it should loop over, the second is contents of each cell, with #1 being replaced by the column number.

\documentclass[border=3.14]{standalone}

\newcounter{col}

\makeatletter
\newcommand\colloop@add[2]
  {%
    \expandafter\g@addto@macro\expandafter\colloop@\expandafter
      {\colloop@@{#1}}%
    \ifnum#2>\value{col}
      \g@addto@macro\colloop@{&}%
    \fi
  }
\newcommand\colloop[2]
  {%
    \noalign
      {%
        \setcounter{col}{0}%
        \gdef\colloop@{}%
        \gdef\colloop@@##1{#2}%
        \loop\ifnum#1>\value{col}
          \stepcounter{col}%
          \expandafter\colloop@add\expandafter{\the\value{col}}{#1}%
        \repeat
      }%
    \colloop@
  }
\makeatletter

\begin{document}
\begin{tabular}[]{llll}
  \colloop{4}{column #1} \\
  \colloop{4}{\multicolumn{1}{c}{c#1}} \\
  \colloop{4}{\multicolumn{1}{r}{col#1}} \\
\end{tabular}
\end{document}

enter image description here

EDIT: Alternative approach which is split in an expandable and an unexpandable part. With \Setupexpcolloop and \setupexpcolloop you can define what \expcolloop should expand to (again using #1 for the current column). The difference between the two defining macros is, that \setupexpcolloop can be used anywhere while \Setupexpcolloop can be used only at the start of a row. But if you use \setupexpcolloop you can't use \multicolumn or similar in the same cell (so use it outside of the tabular environment, or at the end of a cell which is already filled with contents). The advantage of this approach is, that you don't have to loop over an entire row or only the first n columns.

\documentclass[border=3.14]{standalone}

\makeatletter
\long\def\myfi@b\fi#1#2{\fi#2}
\newcommand\setupexpcolloop[1]
  {%
    \gdef\expcolloop@##1{#1}%
  }
\newcommand\Setupexpcolloop[1]
  {%
    \noalign{\setupexpcolloop{#1}}%
  }
\newcommand\expcolloop[2]
  {%
    \expcolloop@{#1}%
    \ifnum#1<#2
      \myfi@b
    \fi
    \@gobble
    {&\expandafter\expcolloop\expandafter{\the\numexpr#1+1\relax}{#2}}%
  }
\makeatletter

\begin{document}
\begin{tabular}[]{|l|l|l|l|l|l|}
  \Setupexpcolloop{column #1}
  \expcolloop{1}{6} \\
  \Setupexpcolloop{\multicolumn{1}{c|}{c#1}}
  col1 & \expcolloop{2}{6} \\
  \Setupexpcolloop
    {%
      \ifnum#1=1
        \multicolumn{1}{|c|}{hihi1}%
      \else
        \multicolumn{#1}{c|}{hihi#1}%
      \fi
    }%
  \expcolloop{1}{3} \\
  \Setupexpcolloop{c#1}
  \expcolloop{1}{2}\setupexpcolloop{Cc#1} & \expcolloop{3}{6} \\
\end{tabular}
\end{document}

enter image description here

Disclaimer: I do not endorse the usage of vertical rules in tables, this is just to show the effects of the \multicolumn usages.


Just because it is possible, here is another variant which is fully expandable and doesn't need to be split. This makes use of expkv-cs to parse a key=value list expandably, and of etl to expandably replace a token (here we use # to insert the column number; if you need # inside your looped columns for any reason you can use another single token with the token or t key).

\documentclass[border=3.14]{standalone}

\usepackage{etl}
\usepackage{expkv-cs}

\ExplSyntaxOn
\NewExpandableDocumentCommand \colloop { O{} m m }
  {
    \exp_args:Ne \exp_not:o
      {
        \exp_not:N \use_none:n
        \__emoro_colloop:nnn {#1} {#2} { & #3 }
      }
  }

\ekvcSplitAndForward \__emoro_colloop:nnn \__emoro_colloop:nnNnn % define keys
  {
     long ~ start = 1
    ,long ~ step  = 1
    ,long ~ token = #
  }
\ekvcSecondaryKeys \__emoro_colloop:nnn % define shortcut names for keys
  {
     alias ~ s = step
    ,alias ~ t = token
  }
\NewDocumentCommand \colloopsetup { m }
  { \ekvcChange \__emoro_colloop:nnn {#1} }
\makeatletter
\cs_if_exist:NF \ekvletunknownNoVal
  {
    \cs_new_protected:Npn \ekvletunknownNoVal #1
      {\expandafter\let\csname\ekv@name{#1}{}uN\endcsname}
  }
\exp_args:Nnc \ekvletunknownNoVal
  { \token_to_str:N \__emoro_colloop:nnn }
  { \ekv@name { \token_to_str:N \__emoro_colloop:nnn } { start } }
\makeatother
\cs_new:Npn \__emoro_colloop:nnNnn #1#2#3#4#5
  {
    \emoro_int_step_tokens:nnnn {#1} {#2} {#4}
      { \etl_token_replace_all_deep:nNn {#5} #3 }
  }

\msg_new:nnn { emoro } { zero-step } { Zero~ step~ size }
\cs_new:Npn \emoro_int_step_tokens:nnnn #1#2#3#4
  {
    \int_compare:nNnTF {#2} > \c_zero_int
      { \__emoro_int_step_tokens:Nfffn > }
      {
        \int_compare:nNnTF {#2} = \c_zero_int
          { \msg_expandable_error:nn { emoro } { zero-step } \prg_break: }
          { \__emoro_int_step_tokens:Nfffn < }
      }
      { \int_eval:n {#1} }
      { \int_eval:n {#2} }
      { \int_eval:n {#3} }
      {#4}
    \prg_break_point:
  }
\cs_new:Npn \__emoro_int_step_tokens:Nnnnn #1#2#3#4#5
  {
    \if_int_compare:w #2 #1 #4 \exp_stop_f: \prg_break:n \fi:
    \use:n {#5} {#2}
    \exp_args:NNf \__emoro_int_step_tokens:Nnnnn #1
      { \int_eval:n { #2 + #3 } }
      {#3}
      {#4}
      {#5}
  }
\cs_generate_variant:Nn \__emoro_int_step_tokens:Nnnnn { Nfff }
\ExplSyntaxOff

\begin{document}
\begin{tabular}{lllll}
  \colloop{5}{column #} \\
  \colloop[11]{15}{\multicolumn{1}{c}{c#}} \\
  \colloop[s=2]{5}
    {%
      \ifnum#=1
        \multicolumn{1}{r}{hihi1}%
      \else
        \multicolumn{2}{r}{hihi#}%
      \fi
    } \\
  \colloop[5,s=-1]{1}{and #}
\end{tabular}
\end{document}
2
  • So, like I could suppose, the paradigm I explored was finished. Your new point of view, colloop comand and their extensions work fine! I also take note of your beer-licence ;)
    – e_moro
    May 12, 2019 at 11:47
  • @e_moro the beerware license is primarily for code that gets published in packages or the like (the whole relicense thread is primarily for that). If you use it in your document and don't publish the code you don't have to strictly follow that license. Of course I wouldn't say no to a beer :)
    – Skillmon
    May 12, 2019 at 16:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .