长期读者,第一次发帖。
我有一个关于创建遍历列表的宏的问题。我读过其他用户发布的一些问题,但这些问题都与以复杂的方式使用 LaTeX 有关。我的问题是关于使用纯 TeX 遍历列表并通过修改旧列表中的每个元素来构造新列表。我目前正在自学 TeX,我认为编写一些基本但通用且强大的宏将有助于我更好地了解 TeX 的核心工作原理。无论如何,背景知识足够了,现在开始回答我的问题。
这是我目前的代码:
我以递归方式定义了迭代器。它每次读取一个参数,确定该参数是逗号还是终止标记(我自己定义的,称为 \myStop),如果是逗号则继续,如果是终止标记则停止,否则用自身和字符串(或标记列表)“+ 1”替换元素。
\edef\myStop{)}
\def\Iterator#1{
\ifx\myStop#1 %do nothing {end of list}
\else %
\ifx,#1 \expandafter\IteratorIntermediateOne % currently 2 if-statements deep
\else #1 + 1, \expandafter\IteratorIntermediateOne %currently 2 if-statements deep
\fi%
\fi%
}
我必须定义一个中间迭代器来IteratorIntermediateOne
容纳命令,\expandafter\Iterator
因为我目前不知道如何在\expandafter
命令后对术语进行分组,其方式与类似\expandafter{\expandafter\Iterator}\fi\fi
. 所以我想这是我的第一个问题:有没有办法定义嵌套\expandafter
命令?
现在一切都已就绪,下面是我所有的代码:
\edef\MyList{1,2,3}
\edef\myStop{)}
\def\IteratorIntermediateOne{\expandafter\Iterator}
\def\Iterator#1{%
\ifx\myStop#1 %do nothing {end of list}
\else %
\ifx,#1 \expandafter\IteratorIntermediateOne % currently 2 if-statements deep
\else #1 + 1, \expandafter\IteratorIntermediateOne %currently 2 if-statements deep
\fi%
\fi%
}
\edef\newList{\expandafter\Iterator\MyList\myStop}
免责声明:我知道最后一个元素后面有一个额外的逗号。我也知道没有足够的案例来检测构造不良的列表。我对 TeX 了解不够多,甚至无法想象如何保护宏免受构造不当的标记列表的影响,所以如果你们中更有经验的人忍不住嘲笑这段代码,我深表歉意。
好的,我的另一个问题是:有没有更有效的方法来定义一个调用自身的宏?单独定义一个迭代器宏并使用 TeX 的内置\loop
命令调用它是否更好?如果是这样,有人可以告诉我该怎么做吗,因为我很难理解 TeX 的眼睛、嘴巴、食道和胃部过程上下文中的循环调用。循环会展开宏,将\repeat
其传递给胃部并返回到宏吗?我找不到任何地方能给出很好的解释。
感谢你的帮助!
答案1
目的似乎是给每个项目加 1 我会像这样编码(假设 etex)
\edef\MyList{1,2,3,25,456,2}
\def\Iterator#1{\expandafter\xiterator#1\stopiteration,}
\def\xiterator#1,{\the\numexpr#1+1\relax,\xiterator}
\def\stopiteration#1\relax#2\xiterator{#1\relax}
\message{\Iterator\MyList}
\bye
这使得信息
2,3,4,26,457,3
答案2
\input listofitems
\def\MyList{1,2,3,25,456,2}
\def\processlist#1{%
\readlist\myterms\MyList
\foreachitem\z\in\myterms{%
\ifnum\zcnt=1\else,\fi
\the\numexpr\z+1\relax
}%
}
\processlist\Mylist
\bye
如果你确实需要节省更新后的列表,我们可以在\mytoks
代币列表中进行以下操作:
\input listofitems
\newtoks\mytoks
\def\MyList{1,2,3,25,456,2}
\def\processlist#1{%
\mytoks{}%
\readlist\myterms\MyList
\foreachitem\z\in\myterms{%
\ifnum\zcnt=1\else\mytoks\expandafter{\the\mytoks,}\fi
\mytoks\expandafter\expandafter\expandafter{%
\expandafter\the\expandafter\mytoks\the\numexpr\z+1\relax}
}%
}
\processlist\Mylist
List is \the\mytoks
\bye
答案3
你说:
...所以如果你们中更有经验的人忍不住嘲笑这段代码,那么请原谅。
当我开始学习 TeX 时,我感觉学习曲线非常陡峭。
我时不时会感到沮丧。;-)
我不相信像你这样踏上这条学习曲线的人会适合嘲笑他们进行宏编程/TeX 编程的尝试。
我认为,任何试图以理性为基础、本身并无过错的方式去实现或学习某些好东西的尝试,基本上都是值得尊重的。
如果您对我下面示例中的代码如何工作有疑问,请随时提出。描述您认为代码如何工作以及您在理解中遇到哪些困难是很有用的。根据我的经验,这可以让受访者更容易地找出哪些信息(例如关于 TeX 基元如何工作以及 TeXbook 后面章节中简要提到的哪些“副作用”用于编程技巧)仍然缺失。
假设逗号列表中的条目没有被空格包围,并且 -primitive\relax
不会出现在逗号列表中,并且\numexpr
ε-TeX-extensions 可用,您可能可以执行以下操作:
\long\def\gobble#1{}%
\long\def\firstofone#1{#1}%
\def\Iterator#1#2,{%
% #1 - element-separator to prepend; empty in the 1st iteration;
% comma in consecutive iterations
% #2 - either current element of old list or the \relax that was
% appended for denoting the end of the list
\ifx\relax#2\expandafter\gobble\else\expandafter\firstofone\fi
{%
#1\number\numexpr#2+1\relax\Iterator{,}%
}%
}%
\def\MyList{1,2,3}
\edef\newList{\expandafter\Iterator\expandafter{\expandafter}\MyList,\relax,}
\begingroup
\tt
\string\MyList: \meaning\MyList
\string\newList: \meaning\newList
\endgroup
\bye
这个例子的要点是:
在TeX-primitive\edef
的 -definition-text中用于扩展。还附加了序列。\newList
\expandafter
\MyList
,\relax,
这样,在定义时,\newList
\edef
对定义文本的驱动扩展在\newList
某个阶段会产生一个序列\Iterator{}Comma,sparated,items,from,\MyList,\relax,
。
这\relax,
标志着列表的结束。
\edef
现在——仍然由-expansion驱动—— \Iterator
(递归地)选择一个非分隔参数#1
(在第一次迭代中为空,在连续迭代中保存一个逗号,即,它保存要添加到新列表项前面的分隔符)和一个逗号分隔参数,#2
该参数要么保存来自\myList
逗号列表的下一个项,要么保存结束标记\relax
,并且在任何情况下放置——嵌套在花括号中——表示下一次迭代的标记序列,由以下形式形成
- 未限定参数
#1
,即新列表的下一个项目之前的分隔符, - 将逗号分隔参数所表示的值加 1 的表达式
\number\numexpr#2+1\relax
,以此形成新列表的下一个项目, - 调用自身来处理扩展后剩下的下一个项
\myList
,这次在未分隔的参数内提供一个逗号,表示下次新列表的下一个项前面要有一个逗号。
通过\ifx\relax#2
检查是否到达逗号列表的末尾/\relax
附加到扩展开头的列表。如果是,则嵌套在花括号中的表示下一次迭代的标记序列通过“吞噬/移除”而不再执行,从而终止迭代/尾部递归。如果不是,则通过应用从该序列中删除周围的花括号,然后处理该序列。\edef
\gobble
\firstofone
#1
的未定界参数\Iterator
(用于保存要添加到新列表项前面的分隔符)仅在第一次迭代中为空。在每次连续迭代中,它都保存一个逗号,该逗号在该连续迭代的上一次迭代中由\Iterator
-macro 本身作为标记序列的一部分提供,然后形成下一次迭代。这样(仅)新列表的第一个项前面没有逗号。
如果您没有\numexpr
可用的 ε-TeX 扩展,我可以提供一个用于增加非负整数的例程。(在“现实生活中”,您可能会对这些包感兴趣计算和bigintcalc。
%------------------------------------------------------------------------------
% Expandable incrementing of non-negative integer number formed by a sequence
% of explicit catcode-12-character-tokens from the set {0,1,2,3,4,5,6,7,8,9}
%..............................................................................
% \Increment{<non-negative integer number k as sequence of explicit
% catcode-12-character-tokens from the set 0123456789>}
% ->
% <natural number (k+1) as sequence of explicit catcode-12-character-tokens
% from the set 0123456789>
% In expansion-contexts the result is delivered after two expansion-steps/is
% obtained by "hitting" \Increment with \expandafter twice.
%------------------------------------------------------------------------------
\def\Increment#1{%
\romannumeral0%
\IncrementReverse{\IncrementFork{}}{\relax}{}#1\relax
}%
\def\IncrementReverse#1#2#3#4{%
% #1 - tokens to prepend to reversed list
% #2 - tokens to append to reversed list
% #3 - reversed list constructed so far
% #4 - current element of not-reversed list
\ifx\relax#4%
\expandafter\firstoftwo
\else
\expandafter\secondoftwo
\fi
{#1#3#2}{\IncrementReverse{#1}{#2}{#4#3}}%
}%
\def\IncrementSelect#10123456789\relax#2#3!!{#2}%
\def\IncrementFork#1#2{%
% #1 - digits incremented so far
% #2 - current digit to increment or end-marker \relax
\IncrementSelect
#2123456789\relax{\IncrementReverse{ }{}{}#11}%
0#223456789\relax{\IncrementReverse{ }{}{}#12}%
01#23456789\relax{\IncrementReverse{ }{}{}#13}%
012#2456789\relax{\IncrementReverse{ }{}{}#14}%
0123#256789\relax{\IncrementReverse{ }{}{}#15}%
01234#26789\relax{\IncrementReverse{ }{}{}#16}%
012345#2789\relax{\IncrementReverse{ }{}{}#17}%
0123456#289\relax{\IncrementReverse{ }{}{}#18}%
01234567#29\relax{\IncrementReverse{ }{}{}#19}%
012345678#2\relax{\IncrementFork{#10}}%
0123456789#2{\IncrementReverse{ }{}{}#11\relax}%
0123456789\relax{\IncrementReverse{ }{}{}#11#2}%
!!%
}%
%%-----------------------------------------------------------------------------
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\def\Iterator#1#2,{%
% #1 - element-separator to prepend
% #2 - current element of old list
\ifx\relax#2\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{}{%
#1\Increment{#2}\Iterator{,}%
}%
}%
\def\MyList{1,2,3}
\edef\newList{\expandafter\Iterator\expandafter{\expandafter}\MyList,\relax,}
\begingroup
\tt
\string\MyList: \meaning\MyList
\string\newList: \meaning\newList
\endgroup
\bye
如果您希望例程不执行\edef
,那么您可以使用\romannumeral0
扩展和参数交换技术 - 扩展的要点\romannumeral0
是:
- TeX 在收集属于⟨数字⟩- 用罗马数字表示的数量。
- 如果 TeX 在收集⟨数字⟩-数量是一个数字,例如,
0
然后收集属于的代币的过程⟨数字⟩-quantity 变成了收集更多数字或非数字的过程,因此终止了收集过程。可扩展标记在收集数字时会进行扩展。终止数字序列的空格标记会终止收集更多数字的过程并被默默丢弃。 - 如果收集到的数字不为正数,TeX 将会默默地吞掉构成⟨数字⟩-数量,但不提供任何代币作为回报。
这意味着\romannumeral
可以用来欺骗 TeX 做大量的扩展和参数交换工作,只要确保最终找到一个非正数。
%------------------------------------------------------------------------------
% Expandable incrementing of non-negative integer number formed by a sequence
% of explicit catcode-12-character-tokens from the set {0,1,2,3,4,5,6,7,8,9}
%..............................................................................
% \Increment{<non-negative integer number k as sequence of explicit
% catcode-12-character-tokens from the set 0123456789>}
% ->
% <natural number (k+1) as sequence of explicit catcode-12-character-tokens
% from the set 0123456789>
% In expansion-contexts the result is delivered after two expansion-steps/is
% obtained by "hitting" \Increment with \expandafter twice.
%------------------------------------------------------------------------------
\def\Increment#1{%
\romannumeral0%
\IncrementReverse{\IncrementFork{}}{\relax}{}#1\relax
}%
\def\IncrementReverse#1#2#3#4{%
% #1 - tokens to prepend to reversed list
% #2 - tokens to append to reversed list
% #3 - reversed list constructed so far
% #4 - current element of not-reversed list
\ifx\relax#4%
\expandafter\firstoftwo
\else
\expandafter\secondoftwo
\fi
{#1#3#2}{\IncrementReverse{#1}{#2}{#4#3}}%
}%
\def\IncrementSelect#10123456789\relax#2#3!!{#2}%
\def\IncrementFork#1#2{%
% #1 - digits incremented so far
% #2 - current digit to increment or end-marker \relax
\IncrementSelect
#2123456789\relax{\IncrementReverse{ }{}{}#11}%
0#223456789\relax{\IncrementReverse{ }{}{}#12}%
01#23456789\relax{\IncrementReverse{ }{}{}#13}%
012#2456789\relax{\IncrementReverse{ }{}{}#14}%
0123#256789\relax{\IncrementReverse{ }{}{}#15}%
01234#26789\relax{\IncrementReverse{ }{}{}#16}%
012345#2789\relax{\IncrementReverse{ }{}{}#17}%
0123456#289\relax{\IncrementReverse{ }{}{}#18}%
01234567#29\relax{\IncrementReverse{ }{}{}#19}%
012345678#2\relax{\IncrementFork{#10}}%
0123456789#2{\IncrementReverse{ }{}{}#11\relax}%
0123456789\relax{\IncrementReverse{ }{}{}#11#2}%
!!%
}%
%%-----------------------------------------------------------------------------
\long\def\firstoftwo#1#2{#1}%
\long\def\secondoftwo#1#2{#2}%
\long\def\exchange#1#2{#2#1}%
\def\Iterator#1,#2\relax#3#4{%
% #1 - current element of old list
% #2 - remaining elements of old list
% #3 - element-separator to prepend
% #4 - new list constructed so far
\ifx\relax#1\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{ #4}{%
\expandafter\exchange
\expandafter{%
\expandafter{%
\romannumeral0%
\expandafter\expandafter\expandafter\exchange
\expandafter\expandafter\expandafter{%
\Increment{#1}}{ #4#3}}}{\Iterator#2\relax{,}}%
}%
}%
\def\MyList{0,1,2,3}
\expandafter\def
\expandafter\newList
\expandafter{%
\romannumeral0\expandafter\Iterator\MyList,{\relax},\relax{}{}}%
\begingroup
\tt
\string\MyList: \meaning\MyList
\string\newList: \meaning\newList
\endgroup
\bye
答案4
由于您是新手,因此您可以从 开始expl3
。
\documentclass{article}
\usepackage{xparse,xfp}
\ExplSyntaxOn
\NewDocumentCommand{\generatelist}{mmm}
{% #1=output, #2=input, #3=iterator
\harry_list_generate:nnn { #1 } { #2 } { #3 }
}
% variables
\clist_new:N \l__harry_list_input_clist
\clist_new:N \l__harry_list_output_clist
% the main function
\cs_new_protected:Nn \harry_list_generate:nnn
{
% if the input is a single token, assume it is a control sequence
\tl_if_single:nTF { #2 }
{ \clist_set_eq:NN \l__harry_list_input_clist #2 }
{ \clist_set:Nn \l__harry_list_input_clist { #2 } }
% now \l__harry_list_input_clist contains the input
% clear the output list
\clist_clear:N \l__harry_list_output_clist
% map the input list applying the iterator to each item
\clist_map_inline:Nn \l__harry_list_input_clist
{
\clist_put_right:Nx \l__harry_list_output_clist { #3 { ##1 } }
}
% make the output list
\clist_set_eq:NN #1 \l__harry_list_output_clist
}
\ExplSyntaxOff
% two example iterators
\newcommand{\addone}[1]{\inteval{#1+1}}
\newcommand{\addhyphens}[1]{-#1-}
% a control sequence expanding to a list
\newcommand{\List}{1,2,3,41}
\generatelist{\ListA}{\List}{\addone}
\generatelist{\ListB}{1,2,3}{\addhyphens}
\show\ListA
\show\ListB
这输出
> \ListA=macro:
->2,3,4,42.
l.50 \show\ListA
?
> \ListB=macro:
->-1-,-2-,-3-.
l.51 \show\ListB