因此,我正在寻找一种在乳胶中执行某些操作的方法,然后遇到了以下 MWE:
\documentclass{article}
\def\foo{1}
\def\bar#1 #2{%
\expandafter\def\expandafter\foo\expandafter{\foo {}#1}
\let\next\bar%
\ifx#2\relax%
\let\next\relax%
\fi%
\next#2%
}
\bar 2 3 4 \relax
\begin{document}
\foo
\end{document}
它的作用基本上是从foo
等于开始,1
然后连接数字2
,然后输出。3
4
1234
我明白最多这一切都在起作用,但我对两个不同的方面感到困惑(可能是同一个方面?),所以我想知道是否有人可以解释发生了什么。最后,我会提到我试去做,希望能显示出我问这个问题的动机。
基本上,我的第一个困惑与以下内容有关:\def\bar#1 #2
。通常我认为您必须添加一个可选参数,例如\def\bar[2]{#1 #2}
或类似的东西,但这似乎在做一些不同的事情?#1 #2
基本上是说2
设置为#1
然后有一个空格然后3 4
设置为#2
?
我的第二个困惑是与术语有关\next
。这也与循环有关,因为看起来不应该这样?我假设这是\next
操作员在做这件事,所以我想知道这是如何工作的。
我提出这个问题的动机是我想做这样的事情,但要在某个数字之后停止。例如,我希望能够调用:\bar{4} 2 3 4 5 6 7\relax
并让输出为1234
(因此当它到达时它会停止4
)。由于我不理解循环是如何发生的以及它\def
是如何工作的,所以我不确定该怎么做。
提前致谢。
答案1
\next
不是预定义运算符。它只是一个在循环过程中被重新定义多次的临时控制序列。
当被调用时会发生什么\bar
(顺便说一下,这是一个坏名字,因为\bar
是用于数学重音的预定义控制序列)?
它的定义有参数文本#1 #2
,这意味着 TeX 将吸收所有内容,直到第一个空格标记,将其分配给参数#1
,然后吸收进一步的标记或标记的括号列表,将其分配给参数#2
。
接下来执行一长串标记\expandafter
。假设您有输入\bar 2 3 4 \relax
。
在这种情况下#1
是2
并且#2
是3
。那么 TeX 将会执行
\expandafter\def\expandafter\foo\expandafter{\foo {}2}
每个\expandafter
标记被轮流触摸,最后一个标记被\foo
扩展为当前含义,因此你得到
\def\foo{1{}2}
存储这样的定义,然后查看下一行,因此\next
被赋予与 相同的含义\bar
。然后#2
将 和 进行比较\relax
;在这种情况下,测试返回 false,因为#2
是,因此跳过3
之前的文本。它保持原样。\fi
\next#2
所以现在 TeX 面临着
\bar3 4 \relax
同样的事情现在发生了两次,所以我们得出
\def\foo{1{}2{}3{}4}
但现在有区别了,因为#2
现在是\relax
。所以\ifx
测试返回 true,TeX 也返回\let\next\relax
,所以最后一条指令变成
\relax\relax
循环结束。
这不是完成任务的特别好的方法,但它确实有效。顺便说一句,%
行末缺少a \expandafter
,并且接下来的两行中有多余的 a 。
如果您想更早地停止循环,则需要修改宏并引入计数器来索引运行次数。
也许您想要的是更加现代的循环。
\documentclass{article}
\ExplSyntaxOn
\seq_new:N \l__aram_addto_seq
\NewDocumentCommand{\aramaddto}{mom}
{
% first split the final argument at spaces (here ~ stands for a space)
\seq_set_split:Nnn \l__aram_addto_seq { ~ } { #3 }
\IfNoValueTF { #2 }
{% no optional argument, add everything
\tl_set:Nx #1 { \exp_not:V #1 \seq_use:Nn \l__aram_addto_seq { } }
}
{% optional argument, only add as many items as required
\seq_map_indexed_inline:Nn \l__aram_addto_seq
{% ##1 is the current index, ##2 is the item
\int_compare:nTF { ##1 < #2 }
{% still wanting to add
\tl_put_right:Nn #1 { ##2 }
}
{% flush
\seq_map_break:
}
}
}
}
\ExplSyntaxOff
\begin{document}
\newcommand{\fooA}{1}
\aramaddto\fooA{2 3 4}
Should print 1234: \fooA
\newcommand{\fooB}{1}
\aramaddto\fooB[4]{2 3 4 5 6}
Should print 1234: \fooB
\end{document}
在评论之后,这里是实现目标的一种方法,即输入一串数字并将它们全部相乘,当其中一个数字大于规定的上限时可以选择停止。
\documentclass{article}
\usepackage{xfp}
\ExplSyntaxOn
\NewDocumentCommand{\genfactorial}{om}
{
\IfNoValueTF { #1 }
{
\aram_genfactorial:en { \clist_item:nn { #2 } { -1 } } { #2 }
}
{
\aram_genfactorial:nn { #1 } { #2 }
}
}
\seq_new:N \l__aram_genfactorial_seq
\cs_new_protected:Nn \aram_genfactorial:nn
{
\seq_clear:N \l__aram_genfactorial_seq
\clist_map_inline:nn { #2 }
{
\int_compare:nTF { ##1 <= #1 }
{ \seq_put_right:Nn \l__aram_genfactorial_seq { ##1 } }
{ \clist_map_break: }
}
\fp_eval:n { \seq_use:Nn \l__aram_genfactorial_seq { * } }
}
\cs_generate_variant:Nn \aram_genfactorial:nn { e }
\ExplSyntaxOff
\begin{document}
\genfactorial{1,2,3,4}
\genfactorial[45]{1,2,3,4,5,6,45,67,89}
\end{document}
使用逗号分隔列表似乎比用空格分隔项目更好。
更新
随着expl3
2021-05-07 的发布,由于新功能,我们可以使上述内容完全扩展\clist_map_tokens:nn
。
检查以0
默认可选参数进行,假设输入由正数组成。
给定的输入被逐项映射并*<item>
附加,但<item>
如果可选参数为 0 或超出上限,则转换为 1。
\documentclass{article}
\usepackage{xfp}
\ExplSyntaxOn
\NewExpandableDocumentCommand{\genfactorial}{O{0}m}
{
\aram_genfactorial:nn { #1 } { #2 }
}
\cs_new:Nn \aram_genfactorial:nn
{
\fp_eval:n
{
1
\clist_map_tokens:nn { #2 } { \__aram_genfactorial:nn { #1 } }
}
}
\cs_new:Nn \__aram_genfactorial:nn
{
\int_compare:nTF { #1 == 0 }
{% no bound
* #2
}
{% #1 is the upper bound
\int_compare:nTF { #2 <= #1 } { * #2 } { * 1 }
}
}
\ExplSyntaxOff
\begin{document}
\genfactorial{1,2,3,4}
\genfactorial[4]{1,2,3,4,5,6,45,67,89}
\genfactorial[7]{1,2,3,4,5,6,45,67,89}
\genfactorial[45]{1,2,3,4,5,6,45,67,89}
\edef\test{\genfactorial{1,2,3,4,5,6,45,67,89}}\test
\end{document}
最后一行表明该功能确实是完全可扩展的。
答案2
循环更像是递归。它每次使用两个参数。
第一遍:它看到\bar 2 3
;这里#1->2
和#2->3
。数字 2 与 连接\foo
,\next
设置为\bar
(因为#2
不是\relax
)。
然后它被告知要处理\next #2
与 相同的内容\bar 3
。但缺少一个参数!因此,它会查找下一个参数,实际上您处理\bar 3 4
。您再次运行相同的操作,但下一次,您最终会处理\bar 4 relax
。
这次,在进入之后4
,\foo
在中\ifx
你会看到#2
是\relax
,所以\next
被告知不再是\bar
。因此递归停止。