我尝试创建一个将多个字符串连接在一起的命令。第一个源定义了一个新命令\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
}%
这行不通:您需要区分定义宏的时间和执行宏的时间=扩展以获取其替换文本:
在定义宏时,\nmerge
TeX 看不到#
构成宏的定义/替换文本的哈希序列 ( ) 和数字\tempcommand
。在定义宏时,\nmerge
TeX 只看到标记\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
。这是因为\unexpanded
TeX 不断扩展内容,直到“看到” 的花括号⟨一般文本⟩并因此遇到\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
处理时将进一步减少到一个。