9

I need to do something like \def\MyArray{{1, 2, 3, 4}} but with a dynamical number of elements.

The following command makes the string:

\newcommand{\MakeArray}[1]{\{ 1 \foreach \x in {2, ..., #1}{ , \x} \}}

How can I put the expanded result in a macro? I tried doing \edef\MyArray{\MakeArray{4}} but it doesn't compile...

2 Answers 2

7
\documentclass[a4paper]{article}

\makeatletter
\def\MakeArray#1#2{% #1: macro that expands to the list, #2: final number
  \toks@={1}\@tempcnta=\@ne
  \loop\ifnum\@tempcnta<#2\relax
    \advance\@tempcnta\@ne
    \toks@=\expandafter{\the\expandafter\toks@\expandafter,\number\@tempcnta}%
  \repeat
  \edef#1{{\the\toks@}}%
}
\makeatother

\begin{document}

\MakeArray\MyArray{4}

\texttt{\meaning\MyArray} % should print macro:->{1,2,3,4}

\end{document}

Doing this with \foreach is (almost) impossible, because \edef doesn't perform assignments, only expansion, and \foreach does tens of assignments to work. You can see from the code that the \edef I do is just the last thing of the ">1" case (there's no check that the input is sensible, so be careful to say something like \MakeArray\MyArray{x} that would die horribly.

The idea is simple: I put 1 in the token register \toks@ and then loop until the scratch counter \@tempcnta reaches the value #2, augmenting the tokens in \toks@.

The curious line

\toks@=\expandafter{\the\expandafter\toks@\expandafter,\number\@tempcnta}

deserves an explanation. After \toks@= TeX wants to see an open brace (after expansion), so it expands the following token, which is \expandafter; good, this expands after the open brace and the token it finds is \the. Well, \the needs to see some specific kinds of (unexpandable) tokens after it, so it expands in order to see what really comes along; the \expandafter expands the following \expandafter which finally expands \number! Now \the performs its duty, in this case to "free" the contents of \toks@. So, if \@tempcnta=2, TeX is presented with

\toks@={1,2}

exactly what we needed.


A different method, which is much more easily customizable, is using expl3.

\documentclass[a4paper]{article}

\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\MakeArray}{ m m }
 {% #1: macro that expands to the list, #2: final number
  \jeremie_make_array:n { #2 }
  \tl_set_eq:NN #1 \l__jeremie_array_temp_tl
 }

\tl_new:N \l__jeremie_array_temp_tl
\seq_new:N \l__jeremie_array_temp_seq

\cs_new_protected:Npn \jeremie_make_array:n #1
 {
  \seq_clear:N \l__jeremie_array_temp_seq
  \int_step_inline:nnnn { 1 } { 1 } { #1 }
   {
    \seq_put_right:Nn \l__jeremie_array_temp_seq { ##1 }
   }
  \tl_set:Nx \l__jeremie_array_temp_tl { \seq_use:Nn \l__jeremie_array_temp_seq { , } }
 }
\ExplSyntaxOff

\begin{document}

\MakeArray\MyArray{4}

\texttt{\meaning\MyArray} % should print macro:->{1,2,3,4}

\MakeArray\MyArray{1}

\texttt{\meaning\MyArray} % should print macro:->{1}

\MakeArray\MyArray{0}

\texttt{\meaning\MyArray} % should print macro:->

\end{document}

enter image description here

3
  • Thanks! and for the explanation too. Is the special case for 1 for performance reasons?
    – jeremie
    Jul 20, 2011 at 12:15
  • I didn't even try without the special case. Does it work?
    – egreg
    Jul 20, 2011 at 12:19
  • It works fine. Do you want me to edit your answer to remove the special case?
    – jeremie
    Jul 20, 2011 at 15:59
3

Here is an alternative to egreg's answer. It is a slavish imitation of the algorithm, but instead of using token registers and \loop I use \foreach and \edef. It's probably quite a bit slower, as a result, but (since it uses PGF and etoolbox) less intimidating.

\documentclass{article}
\usepackage{pgffor,etoolbox}

% #1 = list macro, #2 = length
\newcommand\MakeArray[2]{%
 \ifnumless{#2}{1}
  {\def#1{}}
  {\def#1{1}%
   \ifnumgreater{#2}{1}
   {%
    \foreach \n in {2,...,#2} {%
     \xdef#1{#1,\n}%
    }%
    \xdef#1{{#1}}%
   }{}%
  }%
}
\begin{document}

Pre-text\MakeArray\MyArray{4}post-text

\texttt{\meaning\MyArray} % should print macro:->{1,2,3,4}

\MakeArray\MyArray{1}\texttt{\meaning\MyArray}

\MakeArray\MyArray{0}\texttt{\meaning\MyArray}

\MakeArray\MyArray{-1}\texttt{\meaning\MyArray}

\end{document}

I put in the pre-text and post-text line just to verify that, indeed, my macro does not produce unwanted spaces. I also cover the special cases when #2 = 1 or #2 < 0, which it looks like egreg was asked to remove (???).

This answer is part of my "campaign" to remind people that any question that is more about programming than about TeX can be answered using PGF. Actually, that campaign is about pgfkeys in particular, so let me give a key-based answer:

\documentclass{article}
\usepackage{pgffor,pgfkeys,etoolbox}

\pgfkeys{
 /make array/.is family, /make array,
 accumulate/.code = {%
  \ifnumgreater{#1}{1}
   {\pgfkeysalso{array/.append = {,#1}}}
   {}%
 },
 initialize/.code = {%
  \ifnumgreater{#1}{0}
   {\pgfkeysalso{array/.initial = 1}}
   {\pgfkeysalso{array/.initial = {}}}%
 },
 do/.style 2 args = {
  initialize = #2,
  accumulate/.list = {1,...,#2},
  array/.get = #1
 }
}

\newcommand\MakeArray[2]{\pgfkeys{/make array, do = #1{#2}}}

\begin{document}

Pre-text\MakeArray\MyArray{4}post-text

\texttt{\meaning\MyArray} % should print macro:->{1,2,3,4}

\MakeArray\MyArray{1}\texttt{\meaning\MyArray}

\MakeArray\MyArray{0}\texttt{\meaning\MyArray}

\MakeArray\MyArray{-1}\texttt{\meaning\MyArray}

\end{document}

This works like this:

  • The \MakeArray macro just calls \pgfkeys with the appropriate key and value, and it handles everything.

  • The key /make array/do just calls a few other keys; it's basically a dispatch function. The names should be self-explanatory. Since pgfkeys has the capability to put key contents into macros, I let it define \MyArray using the .get handler on the array key.

  • The initialize and accumulate keys each handle one of the special-cases tests that I had before. etoolbox is still necessary for readable conditional statements, but now they are meaningfully associated with named subroutines.

  • The way accumulate/.list works is that it calls accumulate on each element of the list. The test in accumulate decides whether a number is worth adding; if #2 <= 1, then it's not. The initialize key has already decided whether 1 is in the list, and otherwise, negative numbers should never be included.

5
  • Nice. I wouldn't use \edef#1: alternating local and global assignments for the same variable can lead to "save stack build-up"; so \xdef#1 is better. Actually yours seems slightly faster.
    – egreg
    Nov 7, 2011 at 18:36
  • @egreg: I would not have expected that (re:faster). Thanks for the tip about save stack buildup; I never think about that, but perhaps I should (is this the next step in TeXpertise?).
    – Ryan Reich
    Nov 7, 2011 at 18:49
  • If I call your macro with 10000, I get 108 save stack words used; with \xdef it drops down to 50; with mine it's 36. Not a big problem, it seems: but a complex document might exhaust the available memory.
    – egreg
    Nov 7, 2011 at 18:52
  • Using \xdef in my macros is slightly faster indeed (your and my methods give similar results).
    – egreg
    Nov 7, 2011 at 18:57
  • @egreg: That's a pretty significant waste of save stack space, indeed. I was just surprised that a high-level, complex construction like \foreach could be faster than something made entirely from TeX primitives (well, \loop is a pretty trivial macro). And I'd thought that token registers were supposed to be faster than macro expansions.
    – Ryan Reich
    Nov 7, 2011 at 19:24

You must log in to answer this question.

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