我有以下命令:\newcommand{triangles}[2]{triangles $\triangle #1$ and $\triangle #2}
,这样会\triangles{ABC}{DEF}
产生triangles $\triangle ABC$ and $\triangle DEF$
。我想扩展该命令,使其可以接受任意数量的参数,并且产生\triangles{ABC}{DEF}{GHI}{JKL}
。triangles $\triangle ABC$, $\triangle DEF$, $\triangle GHI$ and $\triangle DEF$
我曾在某处看到过一个购物清单的解决方案,它利用了\makeatletter
和一些 TeX 命令,但我认为这更困难,因为在添加当前参数之前,您需要知道它是否是最后一个参数(如果不是:请输入逗号,如果是:请输入“and”)。
答案1
您正在讨论的购物清单位于编写一个接受可变数量参数的 LaTeX 宏。
您可以快速调整它以处理任意数量的三角形。还处理只有一个三角形的情况,以便仅显示“三角形”而不是“三角形”。
\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}
这是得到的结果:
编辑
根据以下评论乌尔里希·迪茨,此命令的改进版本为:
\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
不跳过空格并阻止命令后的文本被解释为命令的“参数” \triangles
。
然而,正如egreg 的回答,应该避免这种做事方式。
答案2
具有可变数量参数的宏不是使用 LaTeX 的最佳选择;对于您来说,使用以逗号分隔的三角形列表更简单。
\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}
逗号分隔的列表被映射,并且每个项目都以 的形式添加到序列中$\triangle<vertices>$
。然后使用该序列,并使用指定的分隔符(两个之间、两个以上之间、最后两个之间)。
仅当列表包含至少两个项目时才会添加“s”。
答案3
一种listofitems
方法。注意:我被教导说,对于超过 2 个项目的列表,在“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}
答案4
关于什么
\triangles{ABC}{DEF}{GHI}{JKL} {\large This shall not be part of the triangle-command}
?
为了明确哪些括号组属于\triangles
哪些不属于,我强烈推荐类似这样的语法\triangles{{ABC}{DEF}{GHI}}
。
或者完全没有括号组的语法,如 egreg 所建议的那样,\triangles{ABC,DEF,GHI}
下面的例子展示了一种\romannumeral
基于扩展的语法实现方式\triangles{{ABC}{DEF}{GHI}}
:
\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}
当列表包含两个以上的项目时,如果您希望在“and”前加一个逗号:
\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}