8

How can I access the i'th argument of a \newcommand declaration via an iteration variable?

Assume I want a command \foo like:

\newcommand{\barr}[1]{argument 1 is: #1}

\newcommand{\foo}[6]{
    \foreach \i in {1,...,6}
        \barr{#\i}; %this does not work. \barr{#1}, \barr{#6} do work
}

EDIT: As requested a bit of background. I want a command like \foo{1}{2}{5}{0}{1}{2} to paint 1 ball in the first section, 2 in the second, 5 in the third and so on. Everything works fine, the only thing I was missing was to access the i'th parameter in a convenient way.

mort
  • 1,671
  • I'm not sure that I recognize which \foreach you use since there're more of them. Could you please convert your code into a Minimal (non-)Working Example? – yo' Jan 15 '14 at 16:14
  • Related: http://tex.stackexchange.com/questions/2132/how-to-define-a-command-that-takes-more-than-9-arguments/99271#99271 – Steven B. Segletes Jan 15 '14 at 16:29
  • @StevenB.Segletes: But I think that the problem remains: how do I generate \argxiii from \i? – mort Jan 15 '14 at 16:31
  • See my answer, and let me know if \whiledo is acceptable, or if you require \foreach. – Steven B. Segletes Jan 15 '14 at 16:43
  • If you'd add something about what's your aim, it would be possible to tell more. – egreg Jan 15 '14 at 16:47
  • Using your current input, you're limited in terms of the number of arguments you can easily manage (which is 9, by default). There are ways around it, but using a comma-separated list is much-preferred. Perhaps something like \foo{1,2,5,0,1,2}, where , could be substituted for something else (as in @egreg's answer). I think it would be helpful to see the full context in which you're aiming to use this. You mention columns... are you talking about tabular? If so, show us an example. – Werner Jan 15 '14 at 16:56
  • @Werner: no, no tabular. I am drawing nodes in arbitrary sections. Anyway, Steven's answer is perfect for me. – mort Jan 15 '14 at 16:59

4 Answers4

7

You can't specify a macro that way; the #1, #2 and so on must be literally present at definition's time, because they represent placeholders.


I don't see why limiting to six: you'll probably need the same for five or eight.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\foo}{ O{,} m}
 {
  \mort_add_bar:nn { #1 } { #2 }
 }

\seq_new:N \l_mort_input_seq
\seq_new:N \l_mort_output_seq
\cs_new_protected:Npn \mort_add_bar:nn #1 #2
 {
  % split the input at the comma (or what's in the optional argument)
  \seq_set_split:Nnn \l_mort_input_seq { #1 } { #2 }
  % clear the output section
  \seq_clear:N \l_mort_output_seq
  % put each item inside \bar{...}
  \seq_map_inline:Nn \l_mort_input_seq
   {
    \seq_put_right:Nn \l_mort_output_seq { \bar{ ##1 } }
   }
  % output the sequence, separated by semicolons
  \seq_use:Nn \l_mort_output_seq { ; }
 }
\ExplSyntaxOff

\begin{document}
$\foo{a,b,c}$

% just to show that you can change the
% delimiter and have as many items as you wish
$\foo[.]{a.b.c.e.f.g.h.i}$ 
\end{document}

enter image description here

For drawing a number of objects, here's a possible solution:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\foo}{ O{,} m }
 {
  \mort_add_bar:nn { #1 } { #2 }
 }

\seq_new:N \l_mort_input_seq
\cs_new_protected:Npn \mort_add_bar:nn #1 #2
 {
  \seq_set_split:Nnn \l_mort_input_seq { #1 } { #2 }
  \seq_map_inline:Nn \l_mort_input_seq
   {
    \mort_draw_balls:n { ##1 }
   }
 }
\cs_new_protected:Npn \mort_draw_balls:n #1
 {
  /
  \prg_replicate:nn { #1 } { \textbullet }
  /
 }

\ExplSyntaxOff

\begin{document}
\foo{1,2,5,0,1,2}

% just to show that you can change the
% delimiter and have as many items as you wish
\foo[-]{1-3-4}
\end{document}

enter image description here

egreg
  • 1,121,712
4

egreg's and Steven B. Segletes's solutions are of course awesome. However, if one has a fixed number of arguments it seems a bit over the top.

As an alternative, this works too:

\newcommand{\bar}[1]{argument 1 is: #1}

\newcommand{\foo}[6]{
\foreach \i/\j in {1/#1,2/#2,3/#3,4/#4,5/#5,6/#6}   
    \putinbin{\i}{\j};
}
mort
  • 1,671
4

This uses \whiledo instead of \foreach which may or may not be acceptable to the OP.

\documentclass{article}
\usepackage{stringstrings}
\newcounter{index}
\newcommand{\barr}[2]{argument #1 is: #2\par}
\newcommand\foo[1]{%
  \getargs{#1}%
  \setcounter{index}{0}%
  \whiledo{\theindex < \narg}{%
    \stepcounter{index}%
    \barr{\theindex}{\csname arg\romannumeral\theindex\endcsname}%
  }%
}
\begin{document}
\foo{A B C D EE F GGG H I JJJ K FinalArgument}
\end{document}
  • \whiledo is ok in my case. And this solution rocks! – mort Jan 15 '14 at 16:50
  • 1
    @mort The \getargs macro in stringstrings can be somewhat slow. A much faster version is \getargsC in the readarray package. So if you change the \usepackage and add a C to the macro name, speed will improve. – Steven B. Segletes Jan 15 '14 at 17:17
3

I don't think that TeX-core allows this in a simple way. The simplest way would be probably the following, but it's neither nice nor efficient since you have to put the correct number of terms (otherwise TeX-core complains about invalid parameter).

\newcommand{\bar}[1]{argument 1 is: #1}

\newcommand{\foo}[6]{
    \foreach \i in {1,...,6}
        \bar{\ifcase\i\or#1\or#2\or#3\or#4\or#5\or#6\fi};
}
yo'
  • 51,322