循环中准备宏内容(从 \edef 调用 \foreach)

循环中准备宏内容(从 \edef 调用 \foreach)

我需要做一些类似的事情\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要执行数十次赋值才能工作。您可以从代码中看到,\edefI 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 是否在列表中,否则,负数永远不应包含在内。

相关内容