5

For a pstricks project I have a macro with 9 parameters every of which is to be proccessed by the macro in the same way. Instead of writing the same code 9 times for each parameter I'd obviously rather like to use a loop. To do so, I need to access the parameters by their index. Something like

\def\foo#1{...}

\def\bar#1#2#3#4#5#6#7#8#9{ \foreach \i in {1,...,9}{ \if ... % parameter #\i has some property \bar{#\i} \fi } }

How can I achieve this? (The above code obviously doesn't compile.)

I have searched on the internet for several days and couldn't find anything which surprised me.

Thanks in advance for your help.

6 Answers6

7

Parameter replacement happens only at the time TeX expands the macro, so you cannot access them by number later like you tried to do. If you want arbitrary access to the parameters you have to define temporary macros, so that you can access these macros by name. For example:

\def\bar#1#2#3#4#5#6#7#8#9{%
  % save the parameters:
  \def\bartmpi{#1}%
  \def\bartmpii{#2}%
  \def\bartmpiii{#3}%
  \def\bartmpiv{#4}%
  \def\bartmpv{#5}%
  \def\bartmpvi{#6}%
  \def\bartmpvii{#7}%
  \def\bartmpviii{#8}%
  \def\bartmpix{#9}%
  %
  % some complex computation:
  \def\barnum{5}%
  % access the macro with \csname ..\endcsname
  \csname bartmp\romannumeral\barnum\endcsname
  }

\bar{1}{9}{2}{8}{3}{7}{4}{6}{5}

If you want to just loop through them 1 to 9 then you can just map over the list of parameters and do some action, for example, with \tl_map_inline:nn (inside \tl_map_inline:nn, the current item is ##1):

\ExplSyntaxOn
\cs_new_eq:NN \TlMapInline \tl_map_inline:nn
\ExplSyntaxOff

\def\bar#1#2#3#4#5#6#7#8#9{% \TlMapInline{{#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}{#9}} {% \ifnum##1>5 (##1)% \else [##1]% \fi }% }

\bar{1}{9}{2}{8}{3}{7}{4}{6}{5}

  • Thank you very much, Phelype. I quite like your second approach in terms of simplicity. It's exactly what I was looking for. Though to my shame, despite using LaTeX for many years, I have to admit that I never came across the \tl_map_inline:nn syntax. Is it part of the (La)TeX standard? – user242525 May 19 '21 at 20:15
  • @user242525 \tl_map_inline:nn is part of expl3 (you can find its description in interface3). Since last year, expl3 is part of LaTeX, so no packages are needed. Before that you just have to load \usepackage{expl3}. – Phelype Oleinik May 19 '21 at 20:21
  • Thanks again for the clarification. – user242525 May 19 '21 at 20:26
  • @user242525 You're welcome! Welcome to TeX.SX by the way! – Phelype Oleinik May 19 '21 at 20:28
5

I recommend, instead of using 9 separate arguments, use one argument with internal separators (here a comma ,, which can be changed with \setsepchar, see listofitems documentation)

\documentclass{article}
\usepackage{listofitems}
\begin{document}
\newcommand\foo[2]{%
      \ifnum #1=5   % parameter #\i has some property
         \textbf{#2}/%
      \else
        #2/%
      \fi
}
\newcommand\barr[1]{%
  \readlist*\myargs{#1}%
  \foreachitem\z\in\myargs[]{\foo{\zcnt}{\z}}
}

\barr{a,bb,d, hi mom, \today,zz, d,,!!} \end{document}

enter image description here

Another alternative is to pass the nine arguments to a recursive routine. In this fashion, the calling syntax need not change, if your document already uses it.

\documentclass{article}
\def\foo#1{#1/}
\def\barr#1#2#3#4#5#6#7#8#9{%
  \iftrue\barrhelp{#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}{#9}\fi}
\def\barrhelp#1#2\fi{\fi\foo{#1}\ifx\relax#2\relax\else\barrhelp#2\fi}
\begin{document}
\barr{A}{B}{C}{DD}{E}{Ff}{G}{HHH}{i}
\end{document}

enter image description here

  • Thanks for your suggestion. You are certainly right. I might do that although I would prefer a solution where I don't need to rewrite my code as extensively. – user242525 May 19 '21 at 19:25
  • @user242525 Then you are left with rewriting \barr to successively operate on each of the #1-#9 cases. As Phlype noted, you can't perform the shortcut you were attempting. – Steven B. Segletes May 19 '21 at 19:28
  • @user242525 Please see my edit where I suggest another possible alternative which may be more in line with what you seek. – Steven B. Segletes May 19 '21 at 19:39
  • Thank you. Yes, I was thinking about doing it recursively, but that seems overly complicated for such a simple task. I'm looking for something along the lines of Phelype's second suggestion. Nevertheless, I appreciate your effort and your prompt response. – user242525 May 19 '21 at 19:58
  • Second example: \if is incomplete (nine times). Moreover, \if11 is equal to \iftrue, why do you use \if11 and not \iftrue? Why do you use \fi as a separator and not something else without \iftrue? It seems that it is an attempt to use something similar to my \afterfi macro. – wipet May 21 '21 at 05:42
  • @wipet I was missing a leading \fi in \barrhelp. Thanks for pointing it out. I learned it from David at https://tex.stackexchange.com/questions/372639/trying-to-eliminate-stack-overflow-during-recursion-alphabetic-bubble-sorter/372644#372644. As to \iftrue, I never remember that it exists, but I edited to use it. +1 for the help. – Steven B. Segletes May 21 '21 at 17:24
3

Various tools are used in the answers here. I show another tool, \foreach form OpTeX. Moreover: because the parameters are read to space separator in the \bar macro, you can use \bar with arbitrary number of parameters.

\def\bar#1 {%
  \foreach #1 \do
    {%
      \ifnum##1>5
        (##1)%
      \else
        [##1]%
      \fi
    }%
}
\bar{1}{9}{2}{8}{3}{7}{4}{6}{5}{12}{1}{13}
wipet
  • 74,238
2

As a complement to Phelipe's good answer, here's an abstraction with which you can define commands of that type with any number of arguments up to nine.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\newcyclecommand}{mO{0}m} { \useriiiv_newcyclecommand:nnn { #1 } { #2 } { #3 } }

\tl_const:Nn \c_useriiiv_args_tl { {#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}{#9} } \tl_new:N \l__useriiiv_args_temp_tl \cs_generate_variant:Nn \tl_map_function:nN { nc }

\cs_new_protected:Nn \useriiiv_newcyclecommand:nnn {% #1 = command name, #2 = number of arguments, #3 = template

% set the auxiliary function to the given template \cs_new:cn { __useriiiv_\cs_to_str:N #1_cycle:n } { #3 }

% get the argument list in the form {#1}{#2}... \tl_set:Nx \l__useriiiv_args_temp_tl { \tl_range:Nnn \c_useriiiv_args_tl { 1 } { #2 } }

% define the main function \cs_new:cx { __useriiiv_\cs_to_str:N #1 : \prg_replicate:nn { #2 } { n } } { \exp_not:N \tl_map_function:nc { \l__useriiiv_args_temp_tl } { __useriiiv_\cs_to_str:N #1_cycle:n } }

% define the user level alias \cs_set_eq:Nc #1 { __useriiiv_\cs_to_str:N #1 : \prg_replicate:nn { #2 } { n } } }

\ExplSyntaxOff

\newcyclecommand{\test}[9]{% \ifnum#1>5 (#1)% \else [#1]% \fi }

\newcyclecommand{\testB}[2]{(#1)}

\begin{document}

\test{1}{9}{2}{8}{3}{7}{4}{6}{5}

\testB{1}{9}

\end{document}

The syntax is similar to \newcommand, but the “replacement text” is a template to apply to each argument in the loop, where the current argument is denoted by #1.

enter image description here

Note that if the template consists of fully expandable code, the macro becomes fully expandable. For instance

\edef\x{\test{1}{9}{2}{8}{3}{7}{4}{6}{5}}\show\x

will produce on the terminal

> \x=macro:
->[1](9)[2](8)[3](7)[4](6)[5].
egreg
  • 1,121,712
1

(!!! This answer is "academical"=not intended for a scenario of real usage !!!

\bar is already defined in LaTeX 2ε, therefore below \FOO and \BAR are used instead.)

If you really want this you can have \foreach use a temporary macro (\MYtempadefiner) for defining another temporary macro (\MYtempa) per iteration. Keep in mind that hashes (#) need to be doubled when defining (temporary) macros within \foreach. They also need to be doubled when writing macro-definitions inside macro-definitions.

\documentclass[a4paper]{article}
\usepackage{tikz}

\newcommand\FOO[1]{\par \noindent Argument is: #1}

\newcommand\BAR[9]{% \def\MYtempadefiner##1{% \def\MYtempa####1####2####3####4####5####6####7####8####9{% %\if parameter ##1 has some property% \FOO{##1}% %\fi }% }% \foreach \i in {1,...,9}{% %if (expansion of) \i has some property% \expandafter\MYtempadefiner\expandafter{\expandafter####\i}% \MYtempa{#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}{#9}% %\fi }% }%

\begin{document}

\BAR{1}{2}{3}{4}{5}{6}{7}{8}{9}%

\end{document}

enter image description here

Ulrich Diez
  • 28,770
0

(\bar is already defined in LaTeX 2ε, therefore below \FOO and \BAR are used instead.)

I can offer a macro

\KeepKthOfLArguments{⟨TeX-⟨number⟩-quantity with integer-value K⟩}%
                    {⟨TeX-⟨number⟩-quantity with integer-value L⟩}%
                    ⟨list of L undelimited arguments⟩%

which does the following:

In case there is no ⟨K⟩-th argument in the ⟨list of L undelimited arguments⟩, i.e., in case K<1 or K>L:
Does remove the ⟨list of L undelimited arguments⟩ without returning any token.

In case there is a ⟨K⟩-th argument in the ⟨list of undelimited arguments⟩:
Does deliver that ⟨K⟩-th argument with one level of curly braces removed if present.

Examples:

\KeepKthOfLArguments{0}{5}{A}{B}{C}{D}{E} yields no token at all.

\KeepKthOfLArguments{3}{12}{A}{B}{C}{D}{E}{F}{G}{H}{I}{J}{K}{L} yields: C

\KeepKthOfLArguments{3}{4}{A}{B}{CD}{E} yields: CD

\KeepKthOfLArguments{4}{5}{001}{002}{003}{004}{005} yields: 004

\KeepKthOfLArguments{6}{3}{001}{002}{003} yields no token at all.

\KeepKthOfLArguments{4}{5}{001}{002}{003}{{004}}{005} yields: {004}

Due to \romannumeral-expansion the result is delivered after two expansion-steps/by "hitting" \KeepKthOfLArguments with \expandafter twice/by "hitting" the toplevel-expansion of \KeepKthOfLArguments with \expandafter once.

As ⟨TeX-⟨number⟩-quantity with integer-value K⟩ you can use the "variable" \i of a \foreach-loop:

\errorcontextlines=10000
\documentclass[a4paper]{article}
\usepackage{tikz}

\makeatletter %% Code for \KeepKthOfLArguments: %%============================================================================= %% Paraphernalia: %% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, %% \UD@stopromannumeral, \UD@CheckWhetherNull %%============================================================================= \newcommand\UD@firstoftwo[2]{#1}% \newcommand\UD@secondoftwo[2]{#2}% \newcommand\UD@PassFirstToSecond[2]{#2{#1}}% @ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`^^00}% %%----------------------------------------------------------------------------- %% Check whether argument is empty: %%............................................................................. %% \UD@CheckWhetherNull{<Argument which is to be checked>}% %% {<Tokens to be delivered in case that argument %% which is to be checked is empty>}% %% {<Tokens to be delivered in case that argument %% which is to be checked is not empty>}% %% %% The gist of this macro comes from Robert R. Schneck's \ifempty-macro: %% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J> \newcommand\UD@CheckWhetherNull[1]{% \romannumeral\expandafter\UD@secondoftwo\string{\expandafter \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter \UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{% \expandafter\UD@stopromannumeral\UD@firstoftwo}% }% %%============================================================================= %% Keep only the K-th of L consecutive undelimited arguments. %% ( IF K < 1 OR K > L just remove L consecutive undelimited arguments. ) %%============================================================================= %% \KeepKthOfLArguments{<integer number K>}% %% {<integer number L>}% %% <sequence of L consecutive undelimited arguments> %% %% If L < 1 yields nothing. %% Else: %% If K >= 1 and K < L yields: %% <K-th undelimited argument from <sequence of L consecutive undelimited %% arguments>> %% If K < 1 or K > L %% (-> there is no K-th argument in the %% <sequence of L consecutive undelimited arguments> ) %% yields nothing but removal of <sequence of L consecutive %% undelimited arguments> \newcommand\KeepKthOfLArguments[2]{% \romannumeral % #1: <integer number K> % #2: <integer number L> \expandafter\UD@KeepKthOfLArgumentsKSmallerOneFork \expandafter{\romannumeral\number\number#1 000\expandafter}% \expandafter{\romannumeral\number\number#2 000}% }% %%----------------------------------------------------------------------------- \newcommand\UD@KeepKthOfLArgumentsKSmallerOneFork[2]{% % #1: <K letters m> % #2: <L letters m > \UD@CheckWhetherNull{#1}{% K is smaller than one: \UD@KeepKthOfLArgumentsRemoveNArguments{#2}{\UD@stopromannumeral}{}% }{% K is not smaller than one: \expandafter\UD@PassFirstToSecond \expandafter{% \UD@firstoftwo{}#1% }{% \UD@KeepKthOfLArgumentsEvaluateLMinusKDifferenceLoop{#1}{#2}% }{#2}% }% }% %%----------------------------------------------------------------------------- \newcommand\UD@KeepKthOfLArgumentsEvaluateLMinusKDifferenceLoop[4]{% % #1: <K letters m>
% #2: <L letters m> % (For detecting whether K>L or K<=L, during the loop letters m will % be removed both from #1 and #2 until at least one of these arguments % is empty. % When the loop terminates with 0<K<=L, #1 will be empty and #2 % will hold an amount of letters m corresponding to the the % difference L-K. % When the loop terminates with K>L, #1 will not be empty and #2 % will be empty. % ) % #3: <K-1 letters m> % #4: <L letters m> % (#3 and #4 will be left untouched during the loop so they can be % used for performing appropriate action when loop terminates as % it is known whether K>L.) \UD@CheckWhetherNull{#1}{% We have K<=L: \UD@KeepKthOfLArgumentsRemoveNArguments{% #3% }{% \UD@KeepKthOfLArgumentsRemoveNArguments{#2}{\UD@stopromannumeral}% }{}% }{% \UD@CheckWhetherNull{#2}{% We have K>L: \UD@KeepKthOfLArgumentsRemoveNArguments{#4}{\UD@stopromannumeral}{}% }{% We don't know yet whether K<=L or K>L, thus remove letters m and % do another iteration: \expandafter\UD@PassFirstToSecond \expandafter{% \UD@firstoftwo{}#2% }{% \expandafter\UD@KeepKthOfLArgumentsEvaluateLMinusKDifferenceLoop \expandafter{% \UD@firstoftwo{}#1% }% }{#3}{#4}% }% }% }% %%----------------------------------------------------------------------------- %% \UD@KeepKthOfLArgumentsRemoveNArguments{<N letters m>}% %% {<argument 1>}% %% {<argument 2>}% %% <sequence of consecutive %% undelimited arguments> %%............................................................................. %% Removes the first N undelimited arguments from the <sequence of %% consecutive undelimited arguments>, then inserts
%% <argument 1><argument 2> %% %% On the one hand when providing <argument 2> empty, you can use %% <argument 1> for nesting calls to \UD@KeepKthOfLArgumentsRemoveNArguments. %% On the other hand you can provide a <space token> for stopping %% \romannumeral-expansion as <argument 1> and have the %% macro grab the <K-th undelimited argument> from the <sequence of L %% consecutive undelimited arguments> as <argument 2>. %% \newcommand\UD@KeepKthOfLArgumentsRemoveNArguments[3]{% %% #1: <N letters m>
%% #2: <Argument 1>
%% #3: <Argument 2> \UD@CheckWhetherNull{#1}{#2#3}{% \UD@firstoftwo{% \expandafter\UD@KeepKthOfLArgumentsRemoveNArguments \expandafter{% \UD@firstoftwo{}#1% }{#2}{#3}% }% }% }% %%----------------------------------------------------------------------------- %% End of code for \KeepKthOfLArguments. \makeatother

\newcommand\FOO[1]{% %if #1 has some property \par \noindent Argument is: #1% %\fi }

\newcommand\BAR[9]{% \foreach \i in {1,...,9}{% %if (expansion of) \i has some property \expandafter\expandafter\expandafter\FOO \expandafter\expandafter\expandafter{% \KeepKthOfLArguments{\i}{9}{#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}{#9}% }% %\fi }% }%

\begin{document}

\BAR{A}{B}{C}{D}{E}{F}{G}{H}{I}%

\end{document}

enter image description here

Ulrich Diez
  • 28,770