5

I wish to loop over this code:

\begin{tabular}{l*{8}c}
    first column &
    column a &
    column b &
    column c &
    column d &
    column e &
    column f &
    column g
\end{tabular}

But the following does not compile:

\begin{tabular}{l*{8}c}
    first column
    \foreach \i in {a,b,c,d,e,f,g,h}{%
        & column \i%
    }
\end{tabular}

with an error that looks nothing like something I've written:

! Extra }, or forgotten \endgroup.
<template> \unskip \hfil }
                          \hskip \tabcolsep \endtemplate 
l.40         }

I've hope for a while that this post would help, but I'm not sure that this part of the question was finally answered there.

How do I make this loop work?

  • 2
    essentially you can't make it work using \foreach each table cell is a group and & is like }{ which ends one group and starts a new one, at which point the internal state of the loop is lost. You either need a different loop system that doesn't require local definitions or just build up a macro with the table row then execute that macro at the end of the loop. – David Carlisle May 18 '18 at 14:58
  • @DavidCarlisle Okay, thus the error message about \endgroup :) What "different loop system" are you thinking about? Also, I'm not sure I understand what you mean by "build a macro with the table row" :\ – iago-lito May 18 '18 at 15:11
  • as in the answer in the question that you reference. I have some answers building table rows on site no time to look now, I may answer later if no one else does – David Carlisle May 18 '18 at 15:15
  • @DavidCarlisle Okay, thanks, I'll have a closer look on my side :) – iago-lito May 18 '18 at 15:20
  • Evidently a great question! So many answers in such diversity! Also, I didn't notice your link, but turns out I did actually something along those lines! – Andreas Storvik Strauman May 18 '18 at 18:22
6

You can get a very handy syntax with xparse and expl3. In the second argument #1 stands as a placeholder for the various items in the list.

\documentclass[a4paper]{article}
\usepackage[margin=1.5cm]{geometry}
\usepackage{booktabs}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\firstrow}{mmm}
 {%#1 = first column
  %#2 = format for the other columns
  %#3 = comma separated list of values
  \cs_gset_protected:Nn \__iagolito_temp:n { & #2 }
  #1 \clist_map_function:nN { #3 } \__iagolito_temp:n
 }
\ExplSyntaxOff

\begin{document}

\begin{tabular}{l*{8}c}
\firstrow{first column}{column #1}{a,b,c,d,e,f,g,h} \\
x & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 \\
\end{tabular}

\bigskip

\begin{tabular}{l*{8}c}
\toprule
\firstrow{first column}
  {\begin{tabular}[t]{@{}c@{}}column\\ #1\end{tabular}}
  {a,b,c,d,e,f,g,h} \\
\midrule
x & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 \\
\bottomrule
\end{tabular}

\end{document}

enter image description here

  • Well, cheers :) xparse definitely is powerful! This is the option I like best because it looks the most flexible. – iago-lito May 28 '18 at 8:12
  • 2
    I can't understand it my answer had far more zzz than yours but you get the tick :( – David Carlisle May 28 '18 at 8:35
  • @DavidCarlisle <3 ^ ^ – iago-lito May 28 '18 at 9:26
3

Essentially you can't make it work using \foreach: each table cell is a group and & is like }{ which ends one group and starts a new one, at which point the internal state of the loop is lost. You either need a different loop system that doesn't require local definitions or (like the answer to the question you reference) just build up a macro with the table row then execute that macro at the end of the loop.

This shows both mechanisms, note that in the second one the loop data is managed on the parameter stack and no local (or global) definitions are made, which makes it easier to do the loop "inside" the table.

enter image description here

\documentclass[a4paper]{article}
\usepackage{tikz}
\addtolength\textwidth{2in}
\addtolength\oddsidemargin{-1in}
\begin{document}
\small\centering
\def\zz{first column}
    \foreach \i in {a,b,c,d,e,f,g,h}{%
      \xdef\zz{\zz & column \i}%
    }
\begin{tabular}{l*{8}c}
    \zz
\end{tabular}


\def\zzz#1{\zzza#1,\relax}
\def\zzza#1,#2\relax{&column #1\ifx\relax#2\relax\else\expandafter\zzza\fi#2\relax}

\begin{tabular}{l*{8}c}
first column \zzz{a,b,c,d,e,f,g,h}
\end{tabular}




\end{document}
  • Thanks! I guess this closer from pure TeX, right? Which makes it both pure and.. difficult to read imo ;) – iago-lito May 28 '18 at 8:09
2

Here is an approach using tokens and the listofitems:

Basically it makes a token list from a comma separated list and then calls the token list when you want it (using \the\tabrows):

\documentclass{article}
\usepackage{listofitems}
\makeatletter
\newtoks\tabrows
% Command for adding tokens to list
\newcommand\addrowraw[1]{\tabrows\expandafter{\the\tabrows#1}}
% Command that takes a comma separated list
% of items to be in the column of a row
% The second argument is what to do with the current column
% using the \thiscol command:
\newcommand\addrow[2]{
  % Macro to contain the new list
  \def\newrow{}
  % Read argument as list for listofitems
  \readlist\thecolumns{#1}
  % Iterate and append the ampersand and the content of each column
  \foreachitem\thiscol\in\thecolumns{
    \edef\newrow{\unexpanded\expandafter{\newrow}&#2}
  }
  % Remove first ampersand
  \edef\newrow{\expandafter\@gobble\newrow}
  % Just grouping it
  \edef\newrow{{\newrow}}
  \expandafter\addrowraw\newrow
  % Add the \cr
  \addrowraw{\\}
}
\begin{document}
  % These should be _before_ the tabular environment
  \addrow{a,b,c,d,e,f,g,h}{Col \thiscol, r1}
  \addrow{i,j,k,l,m,n,o,p}{Col \thiscol, r2}
  \begin{tabular}{l*{8}c}
     \the\tabrows
  \end{tabular}

\end{document}

enter image description here

  • Well, thank you. Interesting how you do it with \the\token :) – iago-lito May 28 '18 at 8:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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