4

I'm trying to create a dynamic table with a dynamic number of columns and hspace in it.

Here's what I have so far:

\documentclass{article}
\usepackage{tabularx}
\usepackage{array}
\usepackage{forloop}

\begin{document}
    \edef\rowdescriptor{|c|}
    \edef\pointsdescriptor{}
    \newcounter{tempcounter}
    \forloop{tempcounter}{0}{\value{tempcounter} < 5}{
        \edef\rowdescriptor{\rowdescriptor c|}
        \edef\pointsdescriptor{\expandafter\noexpand\pointsdescriptor & }
    }

    \expandafter\tabular\expandafter{\rowdescriptor}
    \hline
    \pointsdescriptor \\
    \hline
    \endtabular
\end{document}

This produces six boxes, as expected. But, I want to make the boxes wider, while keeping the c's because the actual table has more rows with text that needs to be centered. For this, I'm using \hspace. So, when I change the second line inside the for-loop to

\edef\pointsdescriptor{\expandafter\noexpand\pointsdescriptor & \noexpand{\hspace{0.75cm}}}

I immediately get the error "Use of \forloop doesn't match its definition".

Am I somehow expanding the token wrong? What's the mistake here and how can I do better?

2 Answers 2

2

The forloop package seems to be very old...I recommend using \foreach from pgffor. For recursively building commands I always use \gappto from the etoolbox package. Also, you don't need to build the \rowdescriptor macro because, instead, you can define:

\def\rowdescriptor{*5{|c}|}

The *5{|c} means "repeat the column specification |c 5 times".

Putting this together, the following does what you want:

\documentclass{article}
\usepackage{tabularx}
\usepackage{array}
\usepackage{etoolbox}
\usepackage{pgffor}

\begin{document}
    \def\rowdescriptor{*5{|c}|}
    \foreach \x in {1,...,4}{
        \gappto\pointsdescriptor{\hspace*{0.75cm}&}
    }
    \xappto\pointsdescriptor{\noexpand\hspace*{0.75cm}}
    \expandafter\tabular\expandafter{\rowdescriptor}
    \hline
    \pointsdescriptor \\
    \hline
    \endtabular
\end{document}

I have also used \hspace* as this forces the space to be inserted. Note that it is necessary to add an "extra" \hspace* after the loop because there are 4 &s and 5 columns. Here's the output:

enter image description here

EDIT

Here is a version where the number of columns is hidden in a macro. Of course, you can use \csname ....\endcsname here but I prefer to use \csuse{...} from the etoolbox package because I find this easier to read.

\documentclass{article}
\usepackage{tabularx}
\usepackage{array}
\usepackage{etoolbox}
\usepackage{pgffor}

\begin{document}
    \def\numberofcolumns{5}
    \def\pointsdescriptor{}% initialise
    \xdef\rowdescriptor{*{\csuse{numberofcolumns}}{|c}|}
    \foreach \x in {1,...,\numexpr\csuse{numberofcolumns}-1}{
        \gappto\pointsdescriptor{\hspace*{0.75cm}&}
    }
    \xappto\pointsdescriptor{\noexpand\hspace*{0.75cm}}
    \expandafter\tabular\expandafter{\rowdescriptor}
    \hline
    \pointsdescriptor \\
    \hline
    \endtabular
\end{document}

The output is the same as above. Note that there should be braces in {\csuse{numberofcolumns}} just in case there are more than 9 columns. Also, the \xdef\rowdescriptor is not strictly necessary above because the macro numberofcolumns is "static" but \xdef will be necessary if your actually csname depends on a counter, for example.

Here is a third variation where the code is put inside a macro that takes the number of columns as an optional argument, which defaults to 5:

\documentclass{article}
\usepackage{tabularx}
\usepackage{array}
\usepackage{etoolbox}
\usepackage{pgffor}

\newcommand\mytabular[1][5]{%
    \bgroup%
    \def\rowdescriptor{*{#1}{|c}|}%
    \def\pointsdescriptor{}%
    \foreach \x in {1,...,\numexpr#1-1}{%
        \gappto\pointsdescriptor{\hspace*{0.75cm}&}
    }%
    \xappto\pointsdescriptor{\noexpand\hspace*{0.75cm}}%
    \expandafter\tabular\expandafter{\rowdescriptor}
    \hline
    \pointsdescriptor \\
    \hline
  \endtabular%
  \egroup%
}
\begin{document}
  \mytabular

  \mytabular[7]
\end{document}

This time the output is:

enter image description here

I am not sure what your real application is but I would probably try and use something like this approach.

5
  • Why \xappto instead of \gappto without the \noexpand (or just \appto if you don't need it to be global)?
    – TH.
    Jun 13, 2017 at 8:36
  • 1
    @TH. You're right, \gappto would be better. I"ll edit. As \foreach operates inside a group it is necessary to use \gappto rather than\appto.
    – user30471
    Jun 13, 2017 at 9:51
  • Thanks, this worked beautifully. However, I couldn't do the equivalent of \def\rowdescriptor{*5{|c}|} in my code because the 5 is actually \csname somecommand\endcsname and plugging that in didn't work as intended. Is there some special way to make it work with commands? Jun 13, 2017 at 14:31
  • @HameerAbbasi I have edited my answer to allow for this using \xdef to expand \csname...\endcsnme -- actually, I use \csuse. In addition, I have used \numexpr in the construction of \pointsdescriptor so that it will also accept a variable number of columns
    – user30471
    Jun 14, 2017 at 0:00
  • Awesome! Thanks for all the tips and expanding my (La)TeX knowledge quite a bit. My application was actually generating a marks table for an exam's front page automatically from the number of points for each question. This was the last piece of the puzzle. Jun 14, 2017 at 11:43
3

Your problem is that \expandafter\noexpand\pointsdescriptor will work provided \pointsdescriptor has only one (first) token that need protection from expansion. With e-TeX, this is easily covered using \unexpanded. You also need {\noexpand\hspace{0.75cm}} not \noexpand{\hspace{0.75cm}}, as the latter is applying \noexpand to {, which is already non-expandable. That would mean you'd get

\documentclass{article}
\usepackage{tabularx}
\usepackage{array}
\usepackage{forloop}

\begin{document}
    \edef\rowdescriptor{|c|}
    \edef\pointsdescriptor{}
    \newcounter{tempcounter}
    \forloop{tempcounter}{0}{\value{tempcounter} < 5}{
        \edef\rowdescriptor{\rowdescriptor c|}
        \edef\pointsdescriptor{\unexpanded\expandafter{\pointsdescriptor} & {\noexpand\hspace{0.75cm}}}
    }

    \expandafter\tabular\expandafter{\rowdescriptor}
    \hline
    \pointsdescriptor \\
    \hline
    \endtabular
\end{document}

Notably, this doesn't put in enough spaces: the answer by Andrew already covers the way to solve that (and I think is a preferable way to go generally).

You must log in to answer this question.

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