8

I'm trying to create a matrix using nested \foreach loops. I tried following the example I found in a previous question, but I keep getting errors.

Here is the code I tried to run:

\documentclass{article}
\usepackage{etoolbox}
\usepackage{tikz}
\usetikzlibrary{matrix}

\begin{document}
\let\mymatrixcontent\empty
\newcommand{\row}[1]{
  \foreach \i in {0,...,5} {
    \xappto\mymatrixcontent{\expandonce{
      \node {\i}; & 
    }}
  }
  \xappto\mymatrixcontent{\\}
}
\row{1}

\begin{tikzpicture}
  \matrix[matrix of nodes]{
    \mymatrixcontent
  };
\end{tikzpicture}
\end{document}

I'm getting an error:

! Undefined control sequence.
\\  ->\let \reserved@e 
                       \relax \let \reserved@f \relax \@ifstar {\let \reserv...
l.16 \row{1}

I noticed that this error occurs only after adding the new row after the \foreach loop. Is there a way to avoid it?

EDIT: I completed the example.

haggai_e
  • 456

4 Answers4

13

If you do

\let\mymatrixcontent\empty
\newcommand{\row}{%
  \foreach \i in {0,...,5} {%
    \gappto\mymatrixcontent{\node{\i}; \&}%
    }%
  \gappto\mymatrixcontent{\\}%
}
\row

(using \& is better because of problems with "naked" & tokens; percusse's answer tells how to cope with this when building the matrix) then \mymatrixcontent will expand to

\node{\i}; \& \node{\i}; \& \node{\i}; \& \node{\i}; \& \node{\i}; \&\

and the current definition of \i will be used, which is "print a dotless i", because \i will expand to the successive values only during the action of \foreach. So what's needed is the expansion of \i during the action of \foreach:

\let\mymatrixcontent\empty
\newcommand{\row}{%
  \foreach \i in {0,...,5} {%
    \begingroup\edef\x{\endgroup
       \noexpand\gappto\noexpand\mymatrixcontent{\noexpand\node{\i}; \&}}\x
    }%
  \gappto\mymatrixcontent{\\}%
}
\row

However one has to pay close attention when using \edef: not all tokens are allowed inside the replacement text and those which we want to get as themselves need to be prefixed by \noexpand. In this particular case, where \i is the only token to be expanded, a different method can be used

\let\mymatrixcontent\empty
\newcommand{\row}{%
  \foreach \i in {0,...,5} {%
    \expandafter\gappto\expandafter\mymatrixcontent\expandafter{%
      \expandafter\node\expandafter{\i} \&}}%
    }%
  \gappto\mymatrixcontent{\\}%
}
\row

because the \expandafter chain will cause the expansion of \i before \gappto is executed.


A different approach could be with expl3:

\documentclass{article}
\usepackage{xparse}
\usepackage{tikz}
\usetikzlibrary{matrix}

\ExplSyntaxOn
\NewDocumentCommand{\row}{mmm}
 {%#1 is the macro to define, #2 is the final number, #3 is the code
  \tl_clear_new:N #1
  \int_step_inline:nnnn { 0 } { 1 } { #2 }
   {
    \tl_put_right:Nn #1 { #3 }
   }
  \tl_put_right:Nn #1 { \\ }
 }
\ExplSyntaxOff

\begin{document}
\row{\mymatrixcontent}{5}{\node { #1 }; \& }

\begin{tikzpicture}
  \matrix[matrix of nodes,ampersand replacement=\&]{
    \mymatrixcontent
  };
\end{tikzpicture}
\end{document}

In the third argument to \row, #1 is successively evaluated as 0,...,5 (because 5 is the second argument).

enter image description here

egreg
  • 1,121,712
  • I wasn't able to compile your first solution. I got Too many }'s. – haggai_e Mar 12 '12 at 06:42
  • 2
    Sorry to bother you, but there's still an extra brace in the first solution (the second code segment). I would have deleted it myself, but SO doesn't allow me to do a single character edit. – haggai_e Mar 12 '12 at 08:12
  • Also, you should mention that if I want to use \node inside the \edef, I'll need to put \noexpand there as well. – haggai_e Mar 12 '12 at 08:13
  • @haggai_e Without seeing the "real" code, it's difficult to know what you want to append at every cycle. – egreg Mar 12 '12 at 08:34
  • After Altermundus' comment yesterday, I added an complete example of what I wanted to the question. It didn't include \i initially, but it did have a \node in each column. (I updated it now to include \i as well). – haggai_e Mar 12 '12 at 11:38
  • Is there any way to do this without polluting the global macro space with \gappto? I'd would be great if I could put the whole command including building the matrix inside braces and have nothing leak to a global context. If not is there any general advice about what kind of name one should use when so polluting? – Peter Gerdes Feb 11 '21 at 10:29
  • 1
    @PeterGerdes Use the second approach – egreg Feb 11 '21 at 13:22
  • @egreg Do you mean the expl3 approach? I wasn't sure if that was being defined globally too or not because I’m still working of my latex3 knowledge. Thanks. – Peter Gerdes Feb 12 '21 at 05:09
  • 1
    @PeterGerdes If you place \row inside the tikzpicture, then \mymatrixcontent would be local to it. – egreg Feb 12 '21 at 09:26
  • @egreg I presume you mean in the expl3 version as otherwise \gappto presumably redefines it to be global during the execution of \row correct? Sorry I just want to be sure I’m not misunderstanding you. And thanks! – Peter Gerdes Feb 12 '21 at 10:02
  • 1
    @PeterGerdes Yes, the expl3 version; the \foreach based versions need global assignments. – egreg Feb 12 '21 at 10:26
  • By the way, this will create a seventh column (i.e. adding another column sep to the right and/or a node when nodes in empty cells is true). – Qrrbrbirlbel Aug 02 '23 at 20:57
6

As Martin wrote in the linked answer it's working with \gappto command. Here is an example that works for me. I also changed the ampersand since it's always problematic to put a lonely & into the bowels of expanding macros :)

\documentclass{article}
\usepackage{etoolbox}
\usepackage{tikz}
\usetikzlibrary{matrix}

\begin{document}
\let\mymatrixcontent\empty
\newcommand{\row}[1]{
  \foreach \i in {0,...,5} {
    \gappto\mymatrixcontent{\expandonce{
     A  \& 
    }}
  }
  \gappto\mymatrixcontent{\\}
}
\row{1}

\begin{tikzpicture}
  \matrix (a) [matrix of nodes,ampersand replacement=\&]{
    \mymatrixcontent
  };
    \draw (a-1-1) -- (a-1-5);
\end{tikzpicture}

\end{document}

code output

percusse
  • 157,807
  • Thanks! I had an error when I used \gappto at first, but I guess it was unrelated. – haggai_e Mar 11 '12 at 11:28
  • There's still a problem with this method. For some reason if I put \i after the A, the result is A 1 in all the cells. – haggai_e Mar 11 '12 at 11:41
  • @haggai_e Hmm, that's not 1 but iota, dotless i character. Apparently, \i becomes a command rather than the argument of foreach loop. I'll try to fix it. – percusse Mar 11 '12 at 12:01
  • By the way, this will create a seventh column (i.e. adding another column sep to the right and/or a node when nodes in empty cells is true). – Qrrbrbirlbel Aug 02 '23 at 20:56
2

enter image description here

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{matrix}
\usepackage{etoolbox}

\begin{document} \begin{tikzpicture} \let\mymatrixcontent\empty \foreach \row in {1,...,8}{%% \foreach \col in {1,...,5}{% \xappto\mymatrixcontent{r\row c\col \expandonce{&}} }% \gappto\mymatrixcontent{\} }%%

\matrix[matrix of nodes, ampersand replacement=&, column sep=2ex, label={Matrix} ] (m){ \mymatrixcontent }; \end{tikzpicture} \end{document}

cis
  • 8,073
  • 1
  • 16
  • 45
  • By the way, this will create a sixth column (i.e. adding another column sep to the right and/or a node when nodes in empty cells is true). – Qrrbrbirlbel Aug 02 '23 at 20:56
1

No expansion, no global assignments, no extra packages, no extra column, no ampersand shortcut, just two \foreach loops.

However, the |…| syntax of the matrix library can't be included anymore. But when you generate the cells you can just use \node[…]{…}; directly (or use the various row …/column …/row … column … styles to change the style of rows, columns or cells.

In your case, just use create matrix={1}{1, ..., 5} and set matrix macro=#2. Which will create 1 row with five columns 1, 2, …, 5 but just putting the column numbers in the nodes.

Code

\documentclass[tikz]{standalone}
\tikzset{
  % \protected because \pgfmatrixnextcell and \pgfmatrixendrow expand
  % → can't use |…| shortcut, though
  matrix node/.style 2 args={
    name=\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn,
    alias={\tikzmatrixname'-#1-#2}},
  set matrix macro/.code=
    \protected\def\tikzmatrixcell##1##2{\node[matrix node={##1}{##2}]{#1};},
  set matrix macro'/.code=\protected\def\tikzmatrixcell##1##2{#1},
  set matrix macro=r#1\,c#2,
  create matrix/.style 2 args={
    /tikz/matrix/content/.initial=,
    /tikz/matrix/create rows/.style={
      /tikz/matrix/reset create cell,
      /tikz/matrix/create columns/.style={
        /tikz/matrix/create cell={##1}{####1}},
      /tikz/matrix/create columns/.list={#2},
      /tikz/matrix/content/.append=\pgfmatrixendrow},
    /tikz/matrix/create rows/.list={#1},
    /tikz/node contents=\pgfkeysvalueof{/tikz/matrix/content}},
  matrix/reset create cell/.style={
    /tikz/matrix/create cell/.style 2 args={
      /tikz/matrix/content/.append=\tikzmatrixcell{##1}{##2},
      /tikz/matrix/create cell/.style 2 args={
        /tikz/matrix/content/.append=
          \pgfmatrixnextcell\tikzmatrixcell{####1}{####2}}}}}
\begin{document}
\tikz
\matrix[
  draw, column sep=2ex,
  set matrix macro=#2, create matrix={1}{1, ..., 5}];

\tikz \matrix (m) [ draw, column sep=2ex, row sep=1em, row 1 column 1/.style={set matrix macro=}, column 1/.append style={set matrix macro=row ##1}, row 1/.append style={set matrix macro=column ##2}, set matrix macro=#2#1, % like a spreadsheet create matrix={0, ..., 8}{0, A, B, ..., E}] (m'-1-A) edge[out=0, in=180] (m'-4-B); \end{document}

Output

enter image description here

enter image description here

Qrrbrbirlbel
  • 119,821