虽然已经有一个↗问题对于类似的标题,我冒险再次提问,因为我认为链接的主题有点臃肿,答案/解决方案太复杂了。
这是一个更通用的例子:
\def\recursion#1\nil{%
\ifnum#1>0
\expandafter\recursion\the\numexpr #1-1\relax\nil%
{#1}%
\else%
{Start}{0}%
\fi%
}
\edef\result{\recursion 4997\nil} %works up to 4996
\show\result %`{Start}{0}{1}{2}{3}...'
\bye
它将结果保存{Start}{0}{1}{2}{3}...{<initial value>}
在 中\result
。因此,所需的递归宏预计是可扩展的。
在 TeX Live 的默认设置下,上述代码仅适用于初始值不超过 4996 的情况,但其他情况则会失败! TeX capacity exceeded, sorry [input stack size=5000].
,可能是由于\ifnum...
子句嵌套造成的。
为了解决这个问题,我尝试将递归调用推迟到后面,\fi
如下所示:
\def\recursion#1\nil{%
\ifnum#1>0
\expandafter\firstoftwo%
\else%
\expandafter\secondoftwo%
\fi%
{%
\expandafter\recursion\the\numexpr #1-1\relax\nil%
{#1}%
}{%
{Start}{0}%
}%
}
\def\firstoftwo#1#2{#1}
\def\secondoftwo#1#2{#2}
\edef\result{\recursion 2499\nil}
\show\result %`{Start}{0}{1}{2}{3}...'
\bye
令我惊讶的是,这段代码甚至更早就失败了,即当初始值大于时2498
。
那么,这里出了什么问题?我该如何解决堆栈溢出问题?
答案1
要进行尾部递归调用,您需要从另一端构建结果标记列表:
\def\recursion#1#2{%
\ifnum#1>#2
\expandafter\eatv
\else
{#1}%
\fi
\expandafter\recursion\expandafter{\the\numexpr#1+1\relax}{#2}%
}
\def\eatv#1#2#3#4#5{}
\edef\result{{start}\recursion{0}{7000}} %works up to 4996
\show\result %`{Start}{0}{1}{2}{3}...'
\bye
答案2
说得粗略一点:在递归期间,不要让你的标记直接进入输入堆栈,因为输入堆栈很小。相反,将它们收集在宏参数中,因为宏参数的内存通常要大得多。
\def\recursion#1{\innerrecursion{#1}{}}%
\def\innerrecursion#1#2{%
\ifnum#1>0 %
\expandafter\firstoftwo
\else
\expandafter\secondoftwo
\fi
{%
\expandafter\innerrecursion
\expandafter{%
\number\numexpr #1-1\relax}{{#1}#2}%
}{%
{Start}{0}#2%
}%
}
\def\firstoftwo#1#2{#1}
\def\secondoftwo#1#2{#2}
\edef\result{\recursion{5000}}
\show\result %`{Start}{0}{1}{2}{3}...'
\bye
答案3
适当定义\alex_repeat:n
:
\input expl3-generic.tex
\ExplSyntaxOn
\cs_new:Npn \recursion #1
{
{Start}{0}
\int_step_function:nnnN { 1 } { 1 } { #1-1 } \alex_repeat:n
}
\cs_new:Npn \alex_repeat:n #1 { {#1} }
\ExplSyntaxOff
\edef\result{\recursion{5000}}
\show\result
\bye