我需要做一些类似的事情\def\MyArray{{1, 2, 3, 4}}
,但是使用动态数量的元素。
以下命令生成字符串:
\newcommand{\MakeArray}[1]{\{ 1 \foreach \x in {2, ..., #1}{ , \x} \}}
我怎样才能将扩展结果放入宏中?我试过了,\edef\MyArray{\MakeArray{4}}
但编译不通过……
答案1
\documentclass[a4paper]{article}
\makeatletter
\def\MakeArray#1#2{% #1: macro that expands to the list, #2: final number
\toks@={1}\@tempcnta=\@ne
\loop\ifnum\@tempcnta<#2\relax
\advance\@tempcnta\@ne
\toks@=\expandafter{\the\expandafter\toks@\expandafter,\number\@tempcnta}%
\repeat
\edef#1{{\the\toks@}}%
}
\makeatother
\begin{document}
\MakeArray\MyArray{4}
\texttt{\meaning\MyArray} % should print macro:->{1,2,3,4}
\end{document}
使用 执行此操作\foreach
(几乎)是不可能的,因为\edef
它不执行赋值,只执行扩展,并且\foreach
要执行数十次赋值才能工作。您可以从代码中看到,\edef
I do 只是 ">1" 情况的最后一件事(没有检查输入是否合理,因此要小心,否则\MakeArray\MyArray{x}
会死得很惨。
这个想法很简单:我将1
令牌放入寄存器\toks@
,然后循环,直到临时计数器\@tempcnta
达到值#2
,增加中的令牌\toks@
。
奇怪的线
\toks@=\expandafter{\the\expandafter\toks@\expandafter,\number\@tempcnta}
值得解释一下。TeX\toks@=
希望看到一个左括号(扩展后),因此它会扩展以下标记,即\expandafter
;好的,这会在左括号后扩展,它找到的标记是\the
。好吧,\the
需要看到它后面的一些特定类型的(不可扩展的)标记,因此它会扩展以查看实际出现的内容;扩展\expandafter
以下内容\expandafter
,最终扩展\number
!现在\the
执行其职责,在本例中是“释放” 的内容\toks@
。因此,如果\@tempcnta=2
,TeX 会显示
\toks@={1,2}
正是我们所需要的。
一种不同的方法是使用,这种方法更容易定制expl3
。
\documentclass[a4paper]{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\MakeArray}{ m m }
{% #1: macro that expands to the list, #2: final number
\jeremie_make_array:n { #2 }
\tl_set_eq:NN #1 \l__jeremie_array_temp_tl
}
\tl_new:N \l__jeremie_array_temp_tl
\seq_new:N \l__jeremie_array_temp_seq
\cs_new_protected:Npn \jeremie_make_array:n #1
{
\seq_clear:N \l__jeremie_array_temp_seq
\int_step_inline:nnnn { 1 } { 1 } { #1 }
{
\seq_put_right:Nn \l__jeremie_array_temp_seq { ##1 }
}
\tl_set:Nx \l__jeremie_array_temp_tl { \seq_use:Nn \l__jeremie_array_temp_seq { , } }
}
\ExplSyntaxOff
\begin{document}
\MakeArray\MyArray{4}
\texttt{\meaning\MyArray} % should print macro:->{1,2,3,4}
\MakeArray\MyArray{1}
\texttt{\meaning\MyArray} % should print macro:->{1}
\MakeArray\MyArray{0}
\texttt{\meaning\MyArray} % should print macro:->
\end{document}
答案2
这是 egreg 答案的替代方案。它是对算法的盲目模仿,但\loop
我没有使用令牌寄存器和,而是使用了\foreach
和\edef
。因此,它的速度可能会慢很多,但(因为它使用了 PGF 和etoolbox
)不那么吓人。
\documentclass{article}
\usepackage{pgffor,etoolbox}
% #1 = list macro, #2 = length
\newcommand\MakeArray[2]{%
\ifnumless{#2}{1}
{\def#1{}}
{\def#1{1}%
\ifnumgreater{#2}{1}
{%
\foreach \n in {2,...,#2} {%
\xdef#1{#1,\n}%
}%
\xdef#1{{#1}}%
}{}%
}%
}
\begin{document}
Pre-text\MakeArray\MyArray{4}post-text
\texttt{\meaning\MyArray} % should print macro:->{1,2,3,4}
\MakeArray\MyArray{1}\texttt{\meaning\MyArray}
\MakeArray\MyArray{0}\texttt{\meaning\MyArray}
\MakeArray\MyArray{-1}\texttt{\meaning\MyArray}
\end{document}
我添加了前置文本和后置文本行,只是为了验证我的宏确实不会产生不需要的空格。我还介绍了#2 = 1
或 的特殊情况#2 < 0
,看起来 egreg 被要求删除它们(???)。
此答案是我的“活动”提醒人们,任何与编程有关而不是与 TeX 有关的问题都可以使用 PGF 来回答。实际上,该活动是关于pgfkeys
特定的,所以让我给出一个基于关键的答案:
\documentclass{article}
\usepackage{pgffor,pgfkeys,etoolbox}
\pgfkeys{
/make array/.is family, /make array,
accumulate/.code = {%
\ifnumgreater{#1}{1}
{\pgfkeysalso{array/.append = {,#1}}}
{}%
},
initialize/.code = {%
\ifnumgreater{#1}{0}
{\pgfkeysalso{array/.initial = 1}}
{\pgfkeysalso{array/.initial = {}}}%
},
do/.style 2 args = {
initialize = #2,
accumulate/.list = {1,...,#2},
array/.get = #1
}
}
\newcommand\MakeArray[2]{\pgfkeys{/make array, do = #1{#2}}}
\begin{document}
Pre-text\MakeArray\MyArray{4}post-text
\texttt{\meaning\MyArray} % should print macro:->{1,2,3,4}
\MakeArray\MyArray{1}\texttt{\meaning\MyArray}
\MakeArray\MyArray{0}\texttt{\meaning\MyArray}
\MakeArray\MyArray{-1}\texttt{\meaning\MyArray}
\end{document}
它的工作原理如下:
宏
\MakeArray
只需\pgfkeys
使用适当的键和值进行调用,它就会处理一切。该键
/make array/do
仅调用其他几个键;它基本上是一个调度函数。名称应该是不言自明的。由于pgfkeys
能够将键内容放入宏中,我让它\MyArray
使用键.get
上的处理程序进行定义array
。initialize
和键accumulate
分别处理我之前遇到的特殊情况测试之一。etoolbox
对于可读的条件语句来说仍然是必要的,但现在它们与命名子程序有意义地关联起来。该方法的工作方式
accumulate/.list
是调用accumulate
列表中的每个元素。 中的测试accumulate
决定一个数字是否值得添加;如果#2 <= 1
,则不值得。initialize
键已经决定 1 是否在列表中,否则,负数永远不应包含在内。