2

I have the following command: \newcommand{triangles}[2]{triangles $\triangle #1$ and $\triangle #2}, such that \triangles{ABC}{DEF} produces triangles $\triangle ABC$ and $\triangle DEF$. I would like to expand that command such that it can take any number of arguments and such that \triangles{ABC}{DEF}{GHI}{JKL} produces triangles $\triangle ABC$, $\triangle DEF$, $\triangle GHI$ and $\triangle DEF$. I have seen a solution somewhere for a shoppinglist that makes use of \makeatletter and some TeX command, but this is more difficult, I think, because before you add the current argument, you need to know whether it is the last argument (if not: put a comma, if yes: put "and").

  • For the sake of unambiguity regarding the question which brace groups belong to \triangles and which do not, I strongly recommend a syntax like \triangles{{ABC}{DEF}{GHI}}. Or a syntax without brace-groups at all, as suggested by egreg, \triangles{ABC,DEF,GHI}. – Ulrich Diez Mar 27 at 19:25
4

The shoppinglist you're talking about is at Writing a LaTeX macro that takes a variable number of arguments.

You can adapt it quickly to work with any number of triangles. The case with one triangle is also handled in order to only display "Triangle" instead of "Triangles".

\documentclass[11pt]{article}

\makeatletter
    \newcommand{\triangles}[1]{%
        \checknextarg{#1}}
    \newcommand{\checknextarg}[1]{%
        \@ifnextchar\bgroup{Triangles $\triangle #1$\gobblenextarg}{Triangle $\triangle #1$}}
    \newcommand{\gobblenextarg}[1]{%
        \@ifnextchar\bgroup{, $\triangle #1$\gobblenextarg}{ and $\triangle #1$}}
\makeatother

\begin{document}

\triangles{ABC}

\triangles{ABC}{DEF}

\triangles{ABC}{DEF}{GHI}

\triangles{ABC}{DEF}{GHI}{JKL}

\end{document}

This is the result obtained:

Triangles

EDIT

Following the comment by Ulrich Diez, an improved version of this command would be:

\documentclass[11pt]{article}

\usepackage{ltxcmds}

\makeatletter
    \newcommand{\triangles}[1]{%
        \checknextarg{#1}}
    \newcommand{\checknextarg}[1]{%
        \ltx@ifnextchar@nospace\bgroup{Triangles $\triangle #1$\gobblenextarg}{Triangle $\triangle #1$}}
    \newcommand{\gobblenextarg}[1]{%
        \ltx@ifnextchar@nospace\bgroup{, $\triangle #1$\gobblenextarg}{ and $\triangle #1$}}
\makeatother

\begin{document}

\triangles{ABC}

\triangles{ABC}{DEF}

\triangles{ABC}{DEF}{GHI}

\triangles{ABC}{DEF}{GHI}{JKL}

\triangles{ABC}{DEF}{GHI}{JKL} {\Large This shall not be part of the command}

\end{document}

where \ltx@ifnextchar@nospace does not skip space and prevent the text after the command to be interpreted as an "argument" of the command \triangles.

However, as stated in egreg's answer, this way of doing things should be avoided.

  • 1
    What about \triangles{ABC}{DEF}{GHI}{JKL} {\large This shall not be part of the triangle-command} ? – Ulrich Diez Mar 27 at 19:29
3

Macros with a variable number of arguments are not the best thing to do with LaTeX; in your case it's simpler to use a comma separated list of triangles.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\triangles}{m}
 {
  \bart_triangles:n { #1 }
 }

\seq_new:N \l__bart_triangles_seq
\tl_new:N \l__bart_triangles_item_tl

\cs_new_protected:Nn \bart_triangles:n
 {
  \seq_clear:N \l__bart_triangles_seq
  \clist_map_variable:nNn { #1 } \l__bart_triangles_item_tl
   {
    \__bart_triangles_add:V \l__bart_triangles_item_tl
   }
  triangle
  \int_compare:nF { \seq_count:N \l__bart_triangles_seq < 2 } { s }
  \nobreakspace
  \seq_use:Nnnn \l__bart_triangles_seq { ~and~ } { ,~ } { ~and~ }
 }

\cs_new_protected:Nn \__bart_triangles_add:n
 {
  \seq_put_right:Nn \l__bart_triangles_seq { $\triangle #1$ }
 }
\cs_generate_variant:Nn \__bart_triangles_add:n { V }

\ExplSyntaxOff

\begin{document}

\triangles{ABC}

\triangles{ABC,DEF}

\triangles{ABC,DEF,GHI}

\end{document}

The comma separated list is mapped and each item is added to a sequence in the form $\triangle<vertices>$. Then the sequence is used, with the specified separators (between two, between more than two, between last two).

The “s” is added only if the list contains at least two items.

enter image description here

1

A listofitems approach. Note: I was taught that for a list of more than 2 items, a comma precedes the "and".

\documentclass{article}
\usepackage{listofitems}
\newcommand\triangles[1]{%
  \setsepchar{,}%
  \readlist\trilist{#1}%
  \foreachitem\x\in\trilist[]{%
    $\triangle\x$%
    \ifnum\numexpr\xcnt+1<\listlen\trilist[]\relax%
      ,\ %
    \else%
      \ifnum\numexpr\xcnt+1=\listlen\trilist[]\relax%
        \ifnum\listlen\trilist[]>2\relax,\fi
        \ and\ %
      \fi
    \fi%
  }%
}
\begin{document}
Given \triangles{ABC}.\par
Given \triangles{ABC, DEF}.\par
Given \triangles{ABC, DEF,GHI}.\par
Given \triangles{ABC, DEF,GHI, JKL}.\par
\end{document}

enter image description here

1

What about
\triangles{ABC}{DEF}{GHI}{JKL} {\large This shall not be part of the triangle-command} ?

For the sake of unambiguity regarding the question which brace groups belong to \triangles and which do not, I strongly recommend a syntax like \triangles{{ABC}{DEF}{GHI}}.

Or a syntax without brace-groups at all, as suggested by egreg, \triangles{ABC,DEF,GHI}

The following example exhibits a \romannumeral-expansion-based way of implementing the \triangles{{ABC}{DEF}{GHI}}-syntax:

\documentclass{article}

\makeatletter
%%----------------------------------------------------------------------
\newcommand\UD@gobble[1]{}%
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%%   \UD@ExtractFirstArg{ABCDE} yields  A
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  AB
%%
%% !!! The argument of \UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via \UD@CheckWhetherNull before applying
%% \UD@ExtractFirstArg.
%% The argument of \UD@ExtractFirstArg may contain the token \UD@SelDOm.
%% 
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\UD@exchange#1{ }}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%----------------------------------------------------------------------
%% 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]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%------------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%..............................................................................
%% -- Take advantage of the fact that TeX discards space tokens when
%%    "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that
%%                        argument which is to be checked is blank>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked is not blank}%
\newcommand\UD@CheckWhetherBlank[1]{%
  \romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}.}%
}%
%%------------------------------------------------------------------------------
%% The triangles-list:
%%..............................................................................
\newcommand\triangles[1]{%
  \romannumeral0\UD@CheckWhetherBlank{#1}{ no triangle}{\triangleloop{#1}{}}%
}%
\newcommand\triangleloop[2]{%
  \UD@CheckWhetherBlank{#1}{ #2}{%
    \expandafter\UD@PassFirstToSecond\expandafter{%
      \romannumeral0\expandafter\UD@exchange\expandafter
      {%
        \romannumeral0\expandafter\expandafter\expandafter\UD@exchange
        \expandafter\expandafter\expandafter{\UD@ExtractFirstArg{#1}$}{ $\triangle}%
      }{%
        \expandafter\UD@CheckWhetherBlank\expandafter{\UD@gobble#1}{%
           \UD@CheckWhetherBlank{#2}{ triangle }{ triangles #2 and }%
        }{%
           \UD@CheckWhetherBlank{#2}{ }{ #2, }%
        }%
      }%
    }{%
      \expandafter\triangleloop\expandafter{\UD@gobble#1}%
    }%
  }%
}%
\makeatother


\begin{document}

\triangles{}\par
\triangles{{ABC}}\par
\triangles{{ABC} {DEF} {GHI}}\par
\triangles{{ABC} {DEF}}\par
\triangles{{ABC} {DEF} {GHI} {JKL}}\par

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\ThisTrianglelist
\expandafter\expandafter\expandafter{%
  \triangles{{ABC} {DEF} {GHI} {JKL}}%
}%

\texttt{\frenchspacing|\string\ThisTrianglelist=\meaning\ThisTrianglelist|}

\end{document}

enter image description here


In case you wish a comma before the "and" when the list has more than two items:

\documentclass{article}

\makeatletter
%%----------------------------------------------------------------------
\newcommand\UD@gobble[1]{}%
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%.............................................................................
%%   \UD@ExtractFirstArg{ABCDE} yields  A
%%
%%   \UD@ExtractFirstArg{{AB}CDE} yields  AB
%%
%% !!! The argument of \UD@ExtractFirstArg must not be empty. !!!
%% You can check for emptiness via \UD@CheckWhetherNull before applying
%% \UD@ExtractFirstArg.
%% The argument of \UD@ExtractFirstArg may contain the token \UD@SelDOm.
%% 
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
  \romannumeral0%
  \UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\UD@exchange#1{ }}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%----------------------------------------------------------------------
%% 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]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%------------------------------------------------------------------------------
%% Check whether argument is blank (empty or only spaces):
%%..............................................................................
%% -- Take advantage of the fact that TeX discards space tokens when
%%    "fetching" _un_delimited arguments: --
%% \UD@CheckWhetherBlank{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that
%%                        argument which is to be checked is blank>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked is not blank}%
\newcommand\UD@CheckWhetherBlank[1]{%
  \romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#1{}.}%
}%
%%------------------------------------------------------------------------------
%% The triangles-list:
%%..............................................................................
\newcommand\triangles[1]{%
  \romannumeral0\UD@CheckWhetherBlank{#1}{ no triangle}{\triangleloop{#1}{}{}}%
}%
\newcommand\triangleloop[3]{%
  \UD@CheckWhetherBlank{#1}{ #2}{%
    \expandafter\UD@exchange\expandafter{%
      \romannumeral0\UD@exchange{ }{\expandafter\expandafter\expandafter
      \expandafter\expandafter\expandafter\expandafter}%
      \expandafter\UD@exchange\expandafter{%
         \romannumeral0%
          \expandafter\UD@CheckWhetherBlank\expandafter{\UD@gobble#1}{ {}}{%
             \UD@CheckWhetherBlank{#2}{ {}}{ {,}}%
          }%
      }{\expandafter{%
        \romannumeral0\expandafter\UD@exchange\expandafter
        {%
          \romannumeral0\expandafter\expandafter\expandafter\UD@exchange
          \expandafter\expandafter\expandafter{\UD@ExtractFirstArg{#1}$}{ $\triangle}%
        }{%
          \expandafter\UD@CheckWhetherBlank\expandafter{\UD@gobble#1}{%
             \UD@CheckWhetherBlank{#2}{ triangle }{ triangles #2#3 and }%
          }{%
             \UD@CheckWhetherBlank{#2}{ }{ #2, }%
          }%
        }%
      }}%
    }{%
      \expandafter\triangleloop\expandafter{\UD@gobble#1}%
    }%
  }%
}%
\makeatother


\begin{document}

\triangles{}\par
\triangles{{ABC}}\par
\triangles{{ABC} {DEF}}\par
\triangles{{ABC} {DEF} {GHI}}\par
\triangles{{ABC} {DEF} {GHI} {JKL}}\par

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\ThisTrianglelist
\expandafter\expandafter\expandafter{%
  \triangles{{ABC} {DEF} {GHI} {JKL}}%
}%

\texttt{\frenchspacing|\string\ThisTrianglelist=\meaning\ThisTrianglelist|}

\end{document}

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.