如何正确创建一个采用可变数量参数的命令?

如何正确创建一个采用可变数量参数的命令?

我尝试创建一个将多个字符串连接在一起的命令。第一个源定义了一个新命令\merge,该命令接受两个参数并打印它们,中间没有任何空格。\noindent开头的 可防止段落开头缩进。它运行正常,没有问题。

\documentclass{article}
\begin{document}

\newcommand\merge[2] {%
    \noindent#1#2
}%

\merge{12}{34} \\
\merge{hello}{ nice to meet you} \\
\merge{latex }{is so hard}
\end{document}

输出:

1234
hello nice to meet you
latex is so hard

然后我通过 定义了一个字符串作为命令使用\xdef。它的功能与第一个完全相同,并且运行良好。

\documentclass{article}
\begin{document}

\newcommand\merge[2] {%
    \xdef\tempstring {%
        \noindent#1#2
    }%
    \tempstring
}%

\merge{12}{34} \\
\merge{hello}{ nice to meet you} \\
\merge{latex }{is so hard}
\end{document}

输出:

1234
hello nice to meet you
latex is so hard

最后,我尝试创建一个以整数作为唯一参数的命令\nmergeinit,该命令用于定义另一个\nmerge用于连接n字符串的命令。这次我使用循环\foreach来创建字符串#1#2#3...#n。然后将其放置在\nmerge要执行的位置。要使用此命令,\nmergeinit应始终先调用,以便\nmerge可以获取正确数量的字符串。代码确实编译没有问题,但输出始终是它本身,而不是#k被参数替换#1#2#3...#n

我的猜测是,这\#可以保证传递文字尖锐符号后不会将其解释为参数符号。但是,如果我删除转义反斜杠,代码将无法编译,因为编译器会尝试立即解释尖锐符号。所以我需要找到解决方案,在发出命令时不解释尖锐符号,而是在执行命令时解释尖锐符号。我该怎么做才能使其\nmerge按预期运行?

\documentclass{article}
\usepackage{tikz}
\begin{document}

\newcommand\nmergeinit[1] {%
    \xdef\tempcommand {%
    }%
    \foreach \i in {1,...,#1} {%
        \xdef\tempcommand {%
            \tempcommand\#\i
        }%
    }%
    \providecommand\nmerge {%
    }%
    \renewcommand\nmerge[#1] {%
        \noindent\tempcommand
    }%
}%

\nmergeinit{6}
\nmerge{12}{34}{56}{78}{90}{12} \\
\nmergeinit{5}
\nmerge{hello }{nice }{to }{meet }{you} \\
\nmergeinit{4}
\nmerge{latex }{is }{so }{hard}
\end{document}

预期输出:

123456789012
hello nice to meet you
latex is so hard

实际产量:

#1#2#3#4#5#6
#1#2#3#4#5
#1#2#3#4

答案1

我理解您想在循环中声明一个具有可变数量参数的宏。另一个概念是创建一个在循环中执行的具有单个参数的宏,但这不是您的任务。

您可以通过在循环中将\h设置为 来执行此操作,\relax当构建时,将其设置为 ,当被使用时,将其设置为 。例如,在之后,我们将 展开为。实际上使用 ,因为包括 real (它在 内定义)并且我们想要使用 double ,因为也在内定义。\params##\params\nmergeinit3\h1\h2\h3\params##1##2##3\def\h{####}\h##\nmergeinit#\nmerge\nmergeinit

\newcount\tmpnum
\def\nmergeinit#1{%
   \let\h=\relax \def\params{}
   \tmpnum=0
   \loop
      \ifnum\tmpnum<#1 
         \advance\tmpnum by1
         \edef\params{\params\h\the\tmpnum}
      \repeat
   \def\h{####}
   \expandafter\def\expandafter\nmerge\expanded{\params{\params}}
}

\nmergeinit3
\meaning\nmerge

答案2

在你的尝试中你可以找到

\renewcommand\nmerge[#1] {%
    \noindent\tempcommand
}%

这行不通:您需要区分定义宏的时间和执行宏的时间=扩展以获取其替换文本:

在定义宏时,\nmergeTeX 看不到#构成宏的定义/替换文本的哈希序列 ( ) 和数字\tempcommand。在定义宏时,\nmergeTeX 只看到标记\tempcommand

\nmerge执行 并因此\tempcommand执行 并提供哈希值和数字时,TeX 将不会看到这些哈希值和数字与\nmerge定义宏时宏的参数文本之间的联系\merge。TeX 不会将扩展后的哈希值\tempcommand用作参数的表示\nmerge。这些只是进入标记流的一些哈希值和数字……

你需要得到类似的东西

\renewcommand\nmerge[#1] {%
    \noindent⟨replacement text/toplevel expansion of \tempcommand⟩
}%

因此\tempcommand需要保存适量的哈希值。

这有点棘手,但你的尝试几乎已经成功了:

您可以使用 tikz/pgffor 的-loop在 scratch-macro 的定义文本中\foreach进行累积。##1##2...\tempcommand

如果你这样做你需要了解\unexpanded

\edef当- 或- 定义的定义文本\xdef被展开时,包裹在里面的内容\unexpanded{...}不会展开,但标记\unexpanded和围绕该内容的一对匹配花括号{...}会消失。
\unexpanded以及处理所谓的⟨一般文本⟩)是 TeX 不断扩展内容,直到“看到”⟨一般文本⟩。因此,随着\unexpanded{\foobar}的扩展\foobar被阻止,而随着 的\unexpanded\expandafter{\foobar}扩展 ,扩展后的内容\foobar被阻止,即,您得到所谓的 的顶层扩展\foobar。这是因为\unexpandedTeX 不断扩展内容,直到“看到” 的花括号⟨一般文本⟩并因此遇到\expandafter,进而触发紧跟在花括号后面的标记的顶层扩展\foobar。一旦 的顶层扩展\foobar被传递,\expandafter它就会自行扩展(从而将该标记从标记流中移除),这样 TeX 现在可以“看到”属于 的花\unexpanded括号 ⟨一般文本⟩

如果你这样做,你还需要知道 TeX#在宏扩展期间如何处理哈希():

  • 宏定义中的单个井号后面跟着数字 1..9 之一表示该宏的参数。
  • 如果在宏的定义文本中,哈希值后面没有数字,而是另一个哈希值,则这两个连续的哈希值不会用于表示该宏的参数。在宏扩展期间,这两个哈希值会折叠成一个哈希值。
    例如,\def\foobar#1{#####1}执行\foobar{X}以下操作会产生##X:第一个和第三个哈希值后面分别跟着另一个哈希值,即第二/第四个哈希值 - 这会折叠成被传递的单个哈希值。第五个哈希值后面没有其他哈希值,而是数字1- 这表示宏的第一个参数\foobar
    在宏定义中嵌套宏定义时,您可以/需要对属于内部宏定义宏参数表示的哈希值使用哈希值加倍。
  • 执行\edef- 或\xdef-赋值会伴随扩展构成宏定义文本的标记。因此,嵌套在\unexpanded{...}或 来自\the标记寄存器的 -扩展 之间的哈希值会加倍(并且标记\unexpanded和花括号会被删除)。这是一个不错的功能,因为这样您就可以\unexpanded确保在 宏的定义文本中获得与 之间的单个哈希值一样多的哈希值对\unexpanded{...}。在宏扩展期间,这些哈希值对中的每一对都会折叠成单个哈希值,因此扩展后您将获得与 之间的宏定义文本中相同数量的哈希值\unexpanded{...}
  • \foreach从包含应重复执行的标记的参数中定义一个临时宏。扩展该宏可使该参数中包含的连续哈希值数量减半。

概要:

展开\tempcommand将产生类似的东西#1#2...
因此,的定义文本\tempcommand将由类似的内容组成,##1##2...以便在展开时每对连续的哈希值可以折叠成一个哈希值\tempcommand
定义的指令在 -循环\tempcommand的参数内\foreach,因此需要将哈希值再次翻倍,因为\foreach从该参数中定义了一个临时宏,在其展开期间连续哈希值的数量会减半。
一切都来自于扩展宏\nmergeinit,这意味着需要再次将哈希值翻倍。
总而言之,哈希值需要翻倍三次。将单个哈希值的传出哈希值翻倍三次会产生八个连续的哈希值。为了补偿在-循环扩展
时哈希值数量减半,需要将 的扩展包裹在 之间。\foreach\tempcommand\tempcommand\unexpanded{...}

因此,这里是在定义文本中用于\foreach累积的代码:##1##2...\tempcommand

\documentclass{article}
\usepackage{tikz}

\newcommand\nmergeinit[1]{%
    \gdef\tempcommand{}%
    \foreach \i in {1,...,#1}{%
        \xdef\tempcommand{%
           \unexpanded\expandafter{\tempcommand}########\i
        }%
    }%
    \providecommand\nmerge{}%
    \xdef\tempcommand{%
      \unexpanded{\renewcommand\nmerge[#1]}{\unexpanded\expandafter{\tempcommand}}%
    }%
    \tempcommand
}%

\begin{document}

\nmergeinit{6}
\nmerge{12}{34}{56}{78}{90}{12} \\
\nmergeinit{5}
\nmerge{hello }{nice }{to }{meet }{you} \\
\nmergeinit{4}
\nmerge{latex }{is }{so }{hard}

\end{document}

使用 expl3,类似下面的方法可能会奏效 — x 扩展\exp_args:Nnx需要哈希加倍。这就是为什么\__ally_hashprepend:n定义为在参数数字前面添加两个哈希值:

\documentclass{article}

\ExplSyntaxOn
\cs_new:Npn \nmerge #1 { #1 }
\cs_new:Nn \__ally_hashprepend:n { #### #1 }
\NewDocumentCommand{\nmergeinit}{m}
 {
  \exp_args:Nnx
     \use:nn 
     { \cs_set:Npn \nmerge }
     { \int_step_function:nN { #1 } \__ally_hashprepend:n
        { \int_step_function:nN { #1 } \__ally_hashprepend:n }
     }
 }
\ExplSyntaxOff

\begin{document}

\nmergeinit{6}
\nmerge{12}{34}{56}{78}{90}{12}

\nmergeinit{5}
\nmerge{hello }{nice }{to }{meet }{you}

\nmergeinit{4}
\nmerge{latex }{is }{so }{hard}

\end{document}

使用这些方法,您最多只能连接九个参数。

您可以实现尾部递归机制,其中\romannumeral\nmergeamount000用于获取m所表示的数量\nmergeamount,然后尾部递归地使用m并收集另一个参数,直到所有参数m都被使用:

\documentclass{article}

\csname @ifdefinable\endcsname\stopromannumeral{\chardef\stopromannumeral=`\^^00}%
\newcommand\firstoftwo[2]{#1}%
\newcommand\secondoftwo[2]{#2}%

\newcommand*\nmergeamount{}%
\newcommand*\nmergeinit[1]{\edef\nmergeamount{\number\numexpr(#1)\relax}}%

\newcommand*\nmerge{%
  \romannumeral\expandafter\nmergeloop\expandafter{\romannumeral\nmergeamount000}{}{}%
}%
\newcommand\nmergeloop[3]{%
  \ifx\relax#1\relax\expandafter\secondoftwo\else\expandafter\firstoftwo\fi
  {\expandafter\nmergeloop\expandafter{\firstoftwo{}#1}}%
  {\expandafter\stopromannumeral\secondoftwo{}}%
  {#2#3}%
}%


\begin{document}

\nmergeinit{6}
\nmerge{12}{34}{56}{78}{90}{12} \\
\nmergeinit{5}
\nmerge{hello }{nice }{to }{meet }{you} \\
\nmergeinit{4}
\nmerge{latex }{is }{hard }{hard}

\nmergeinit{15}
% This strips braces from the first 15 of the 20 arguments:
\edef\test{%
  \nmerge{1 }{2 }{3 }{4 }{5 }{6 }{7 }{8 }{9 }{10 }{11 }{12 }{13 }{14 }{15 }{16 }{17 }{18 }{19 }{20 }%
}

{\ttfamily\string\test=\meaning\test}

\end{document}

在此处输入图片描述

答案3

我实在不明白动态(重新)定义的意义\nmerge

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\nmergeinit}{m}
 {
  \cs_set_eq:Nc
   \ally_nmerge:w
   { \int_compare:nT { #1 > 4 } { ally_ } use:\prg_replicate:nn { #1 } { n } }
 }
\nmergeinit{1}

\NewDocumentCommand{\nmerge}{}
 {
  \noindent\ally_nmerge:w
 }

% already defined up to four arguments, we can generalize
\cs_new:Nn \ally_use:nnnnn { #1 #2 #3 #4 #5 }
\cs_new:Nn \ally_use:nnnnnn { #1 #2 #3 #4 #5 #6 }
\cs_new:Nn \ally_use:nnnnnnn { #1 #2 #3 #4 #5 #6 #7 }
\cs_new:Nn \ally_use:nnnnnnnn { #1 #2 #3 #4 #5 #6 #7 #8 }
\cs_new:Nn \ally_use:nnnnnnnnn { #1 #2 #3 #4 #5 #6 #7 #8 #9 }

\ExplSyntaxOff

\begin{document}

\nmergeinit{6}
\nmerge{12}{34}{56}{78}{90}{12}

\nmergeinit{5}
\nmerge{hello }{nice }{to }{meet }{you}

\nmergeinit{4}
\nmerge{latex }{is }{so }{hard}

\end{document}

我们只是使用预定义的命令来完成这项工作。内核expl3只定义了最多\use:nnnn,所以我们可以提供缺少的命令。如果#1大于 4,我们需要访问新命令。

只是为了好玩,这里有一个动态重新定义命令的版本。

这个想法是生成替换文本并将其存储在标记列表变量中。然后我们可以使用变体来定义一个新函数,\cs_new:Nn该函数以函数名称和标记列表作为参数。

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\nmergeinit}{m}
 {
  \tl_set:Nx \l_tmpa_tl { \int_step_function:nN { #1 } \__ally_generate:n }
  \cs_set:cV { __ally_nmerge: \prg_replicate:nn { #1 } { n } } \l_tmpa_tl
  \cs_set:Npx \nmerge
   {
    \noindent \exp_not:c { __ally_nmerge: \prg_replicate:nn { #1 } { n } }
   }
 }
\cs_new:Nn \__ally_generate:n { #### #1 }
\cs_generate_variant:Nn \cs_set:Nn { cV }

\nmergeinit{1} % initialize

\ExplSyntaxOff

\begin{document}

\nmergeinit{6}
\nmerge{12}{34}{56}{78}{90}{12}

\nmergeinit{5}
\nmerge{hello }{nice }{to }{meet }{you}

\nmergeinit{4}
\nmerge{latex }{is }{so }{hard}

\end{document}

#在循环中调用构建标记列表的函数中,我们需要四个\int_step_function:nN;在标记列表中,它们将减少到两个,并且在\cs_new:cV处理时将进一步减少到一个。

答案4

我不清楚这是否能满足您的要求,但该xstring包中有命令\StrRemoveBraces可以删除括号并形成新字符串,您可以将其分配给宏。您只需添加一个额外的一组大括号。

在此处输入图片描述

\documentclass{article}
\usepackage{xstring}

\begin{document}

\StrRemoveBraces{{hello }{nice }{to }{meet }{you}}[\mysentence]
Here is my sentence: \mysentence.

\end{document}

相关内容