我正在尝试制作斐波那契宏,其工作原理如下。
我可以这样做,并且我尝试使用将所有内容设为私有@
(我知道这里没有必要,但我正在尝试练习……)。我知道这\@curtab
应该用于标签,但是因为我看到@David Carlisle 将它用于其他用途,所以我尝试做同样的事情。
这是我写的代码
\begin{document}
\makeatletter
\renewcommand\fib[2][]{\newcount\print@limit \newcount\@limit
\expandafter\ifx\expandafter\relax\detokenize{#1}\relax %no input#1
\print@limit=#2%
\else
\print@limit=#1%
\fi
\@tempcnta\@ne \@tempcntb\@ne \count@#2 \@limit\tw@
\ifnum\print@limit<\tw@ \number\@tempcntb, \fi
\fib@i}
\def\fib@i{%
\ifnum\count@>\@ne
\print@fib
\@curtab=\numexpr\@tempcnta+\@tempcntb\relax
\@tempcnta\@tempcntb \@tempcntb\@curtab
\advance\count@-\@ne \advance\@limit\@ne
\fib@i
\fi}
\def\print@fib{%
\unless\ifnum\@limit<\print@limit%
\number\@tempcntb%
\ifnum\count@>\thr@@ , %
\else\ifnum\count@=\thr@@ ~and %
\fi\fi\fi}
\makeatother
\end{document}
m AND n
它工作得很好,我甚至通过将最后的打印改为而不是使它变得更好m, n
。我的问题是我无法定义\fib
使用\def
,并且不得不求助于使用,\newcommand
因为我无法弄清楚如何使用 获取可选参数\def
。
三个问题
- 我的问题是如何使用
\def
和获取可选参数? - 另外,我想知道是否有更好的方法在每次打印后进行检查
\ifnum>3 , \ifnum=3 and
,以便我得到正确的显示(即除了最后一个数字“and”之外的逗号)。 - 最后,
\loop ... \repeat
我没有使用 ,而是定义了一个\fib@i
,它的作用类似于\loop
。但是,我想知道在这种情况下,使用递归还是循环更好?在另一种代码语言中,我会避免使用递归,因为它只能支持这么多的调用,但在这里,在发生这种情况之前就已经达到了算术溢出,所以我想知道一种方法是否比另一种更好。
答案1
您不应该在每次使用宏时分配两个新计数器(经典 tex 只有 256 个计数器,etex 有更多但仍然......)如果您确实使用计数器,您应该只分配一次,在宏之外,以及您可能在其他地方看到的任何可疑示例,分配您自己的计数器不要重复使用\@curtab
是否使用循环或递归的问题实际上没有答案,因为它\loop
只是一个递归宏,TeX 中没有循环原语。
我可能会做这样的事情(除了可选参数的测试之外还可以扩展)
\documentclass{article}
\makeatletter
\def\fib{\@ifnextchar[\fib@i{\fib@i[\relax]}}
\def\fib@i[#1]#2{%
\ifx\relax#1%
\fib@ii{#2}{#2}101%
\else
\fib@ii{#1}{#2}101%
\fi
}
% #1 hide if less than
% #2 target index
% #3 current index
% #4 #5 last two numbers
\def\fib@ii#1#2#3#4#5{%
\ifnum#1<\numexpr#3+1\relax
\the\numexpr#5\relax
\fi
\ifnum#3<#2\relax
\ifnum#1<\numexpr#3+1\relax, \fi
\fib@ii{#1}{#2}{\numexpr#3+1\relax}{#5}{\numexpr#4+#5\relax}%
\fi}
\makeatother
\begin{document}
%x\tracingall
\fib{20}
\fib[1]{10}
\end{document}
答案2
因为您正在学习,所以我将提出一个不同的(完全可扩展的)解决方案expl3
:
\documentclass{article}
\ExplSyntaxOn
\NewExpandableDocumentCommand{\fib}{O{#2}m}
{
\needle_fib:nn { #1 } { #2 }
}
\cs_new:Nn \needle_fib:nn
{% start the recursion from fib(1)=1, fib(2)=1
% #1 = current step in the recursion
% #2 = starting point
% #3 = end point
% #4 = fib(#1-2)
% #5 = fib(#1-1)
\needle_fib_print:nnnnn { 1 } { #1 } { #2 } { 1 } { 0 }
}
\cs_new:Nn \needle_fib_print:nnnnn
{
\int_compare:nT { #1 >= #2 }
{% the current step is at least the starting point, print the number
\int_eval:n { #4 + #5 }
% a comma-space if not at the last but one
\int_compare:nTF { #3 - #1 = 1 }
{% the first comparison is for the Oxford comma
\int_compare:nF { #3 - #2 = 1 } { , } ~and~ % before the last
}
{ \int_compare:nF { #3 = #1 } { ,~ } } % otherwise a comma-space
}
% recursion
\int_compare:nT { #1 < #3 }
{
\needle_fib_print:ennne
{ \int_eval:n { #1 + 1 } } % increase the step
{ #2 } % starting point
{ #3 } % end point
{ #5 } % previous fibonacci number
{ \int_eval:n { #4 + #5 } }
}
}
\cs_generate_variant:Nn \needle_fib_print:nnnnn { ennne }
\ExplSyntaxOff
\begin{document}
\fib{30}
\fib[19]{20}
\fib[1]{30}
\edef\test{\fib[1]{5}}
\texttt{\meaning\test}
\fib[1]{46}
\end{document}
第 47 个斐波那契数超出了 TeX 的算术能力,除非你bigintcalc
使用我的另一个答案(这是这个的变体)。
\documentclass{article}
\usepackage[margin=1cm,a4paper]{geometry}
\usepackage{bigintcalc,siunitx}
\ExplSyntaxOn
\cs_set_eq:NN \needle_bigint_add:nn \bigintcalcAdd
\NewExpandableDocumentCommand{\fib}{O{#2}m}
{
\needle_fib:nn { #1 } { #2 }
}
\cs_new:Nn \needle_fib:nn
{% start the recursion from fib(1)=1, fib(2)=1
% #1 = current step in the recursion
% #2 = starting point
% #3 = end point
% #4 = fib(#1-2)
% #5 = fib(#1-1)
\needle_fib_print:nnnnn { 1 } { #1 } { #2 } { 1 } { 0 }
}
\cs_new:Nn \needle_fib_print:nnnnn
{
\int_compare:nT { #1 >= #2 }
{% the current step is at least the starting point, print the number
\needle_bigint_add:nn { #4} { #5 }
% a comma-space if not at the last but one
\int_compare:nTF { #3 - #1 = 1 }
{% the first comparison is for the Oxford comma
\int_compare:nF { #3 - #2 = 1 } { , } ~and~ % before the last
}
{ \int_compare:nF { #3 = #1 } { ,~ } } % otherwise a comma-space
}
% recursion
\int_compare:nT { #1 < #3 }
{
\needle_fib_print:ennne
{ \int_eval:n { #1 + 1 } } % increase the step
{ #2 } % starting point
{ #3 } % end point
{ #5 } % previous fibonacci number
{ \needle_bigint_add:nn { #4 } { #5 } }
}
}
\cs_generate_variant:Nn \needle_fib_print:nnnnn { ennne }
\ExplSyntaxOff
\begin{document}
The 200th Fibonacci number is \num{\fib{200}}
\fib{30}
\fib[19]{20}
\fib[1]{30}
\edef\test{\fib[1]{5}}
\texttt{\meaning\test}
\begin{flushleft}
\fib[1]{200}
\end{flushleft}
\end{document}
在图片中我只显示了第一部分,其余部分与另一张图相同。使用\num
本身就表明宏是完全可扩展的。
答案3
-我的问题是如何使用
\def
和获取可选参数?
您可以实现一个基于的循环,用于\futurelet
删除空格标记并查看下一个非空格标记,以确定是否[
存在表示可选参数的标记。这就是\@ifnextchar
LaTeX 2ε 内核所做的。
\catcode`\@=11
\long\def\@firstofone#1{#1}%
\long\def\@ifnextchar#1#2#3{%
\let\reserved@d=#1%
\def\reserved@a{#2}%
\def\reserved@b{#3}%
\futurelet\@let@token\@ifnch
}%
\def\@ifnch{%
\ifx\@let@token\@sptoken
\let\reserved@c\@xifnch
\else
\ifx\@let@token\reserved@d
\let\reserved@c\reserved@a
\else
\let\reserved@c\reserved@b
\fi
\fi
\reserved@c
}%
\@firstofone{\def\@xifnch} {\futurelet\@let@token\@ifnch}%
\def\MacroWithOptArg{%
\@ifnextchar[{\InternalMacroWithOptArg}{\InternalMacroWithNoOptArg}%
}%
\long\def\InternalMacroWithNoOptArg#1{\InternalMacroWithOptArg[{#1}]{#1}}
\long\def\InternalMacroWithOptArg[#1]#2{%
This is the optional argument: #1.
This is the mandatory argument: #2.
}%
\MacroWithOptArg[A]{B}
\MacroWithOptArg{B}
\bye
\MacroWithOptArg
在扩展的某个阶段会产生\@ifnextchar
,这又会产生一堆\def
- 和\futurelet
- 和 -\let
赋值,因此不可扩展。因此,像 一样\@ifnextchar
,\MacroWithOptArg
也不可扩展。
(宏“可扩展”意味着该宏触发的所有处理都仅基于扩展。在 Knuth 的类比中,TeX 是一个有眼睛和消化道的有机体,可扩展宏触发的所有处理都在食道中完成,扩展就发生在食道中。宏“不可扩展”意味着不仅食道而且胃也参与处理。胃是执行作业的地方等。)
与此示例不同,\newcommand
LaTeX 2ε-内核的 -macro 使具有可选参数的宏变得健壮,因此在所谓的“纯扩展上下文”中它们不会执行,因为在纯扩展上下文中执行它们会失败。
-另外,我想知道是否有更好的方法在每次打印后检查
\ifnum>3
,\ifnum=3
并且,以便我得到正确的显示(即除了最后一个数字“和”之外的逗号)。
即兴地,我看不出有更好的方法处理您的代码。但\fib@i
递归调用自身,嵌套在之间\ifnum..\fi
。每次迭代时,另一个\fi
将在输入堆栈中累积。如果您这样做\expandafter\fib@i\fi
,则在执行下\fi
一个实例之前,它们会扩展(并因此被删除),因此不会累积。除此之外,我修改了您的空虚测试,因此不需要它,并且它不依赖于不重新定义——这将是一件非常奇怪的事情,但谁知道呢……\fib@i
\expandafter
\relax
\outer
\documentclass{article}
\makeatletter
\newcount\print@limit \newcount\@limit
\newcommand\fib[2][]{%
\ifcat$\detokenize{#1}$%no input#1
\print@limit=#2%
\else
\print@limit=#1%
\fi
\@tempcnta\@ne \@tempcntb\@ne \count@#2 \@limit\tw@
\ifnum\print@limit<\tw@ \number\@tempcntb, \fi
\fib@i}
\def\fib@i{%
\ifnum\count@>\@ne
\print@fib
\@curtab=\numexpr\@tempcnta+\@tempcntb\relax
\@tempcnta\@tempcntb \@tempcntb\@curtab
\advance\count@-\@ne \advance\@limit\@ne
\expandafter % <- !!!!!!!!!!
\fib@i
\fi}
\def\print@fib{%
\unless\ifnum\@limit<\print@limit%
\number\@tempcntb%
\ifnum\count@>\thr@@ , %
\else\ifnum\count@=\thr@@ ~and %
\fi\fi\fi}
\makeatother
\begin{document}
\fib{20}
\fib[1]{10}
\end{document}
但是,我想知道在这种情况下使用递归还是循环更好?
\loop
是一个递归宏。;-)
但它也有一些缺点:
- 它的参数以 分隔
\repeat
,并进入临时宏。因此,如果\loop
使用 来定义处理参数的宏,则需要对这些参数进行哈希加倍。 - 除非您确切知道自己在做什么,否则最好不要嵌套
\loop..\repeat
构造。分隔符与参数分隔符的匹配很可能\repeat
不会按照您期望的方式进行。;-)
由于 egreg 提出了一个带有 expl3 的完全可扩展解决方案,我认为需要展示可扩展检查是否存在可选参数的缺点:
\documentclass{article}
\ExplSyntaxOn
\NewExpandableDocumentCommand{\macro}{O{#2}m}{
\message{^^J
This~is~the~optional~argument:~[#1]^^J
This~is~the~non~optional~argument:~{#2}^^J^^J
}
}
\ExplSyntaxOff
\begin{document}
\macro[optional]{non-optional}
\macro{[}{optional}]{non-optional}
% The resulting messages should differ but don't.
\end{document}
我明白了
This is the optional argument: [optional]
This is the non optional argument: {non-optional}
两次。
我预计第二条消息是
This is the optional argument: [[]
This is the non optional argument: {[}
以及在结果 .pdf 文件中{optional}]{non-optional}
产生短语的序列。optional]non-optional
为了好玩,我实现了一个变体,它\fib
也可以处理负数,并且在内部\fibloop
调用一个可扩展的尾递归宏。ε-TeX\numexpr
用于进行算术运算,ε-TeX\detokenize
用于检查参数是否为空。
如果上界小于下界,则根本不会打印任何数字。
\documentclass{article}
\makeatletter
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\fib[2][]{%
\expandafter\UD@Exchange\expandafter{\expandafter{\expandafter>\number#2}}{%
\expandafter\UD@Exchange\expandafter{\expandafter{%
\expandafter<\number\ifcat$\detokenize{#1}$%
\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi{#2}{#1}%
}}{\fibloop{0}{0}{1}{}{}}%
}{+}%
}%
\newcommand\fibloop[8]{%
% #1 = n
% #2 = n-th Fibonacci-number
% #3 = (n+1)-th/(n-1)-th fibonacci number
% #4 = comma-space-separated list of Fibonacci-numbers found so far
% #5 = separator to prepend / append
% #6 = < lower bound of range / > upper bound of range = condition for appending Fibonacci-number to list of fibonacci-numbers found so far
% #7 = > upper bound of range / < lower bound of range = condition for ending the loop
% #8 = + / - = operator of arithmetic operations
\ifnum#1#7 \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi{%
\ifx#8+\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\fibloop{-1}{1}{-1}{#4}{#5}{#7}{#6}{-}}{#4}%
}{%
\ifnum#1#6 \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\UD@Exchange{{}{}}}{%
\ifx#8+\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\UD@Exchange{{#4#5#2}{, }}}{\UD@Exchange{{#2#5#4}{, }}}%
}{%
\expandafter\UD@Exchange\expandafter{\expandafter{\number\numexpr#2#8#3\relax}}{%
\expandafter\fibloop\expandafter{\number\numexpr#1#81\relax}{#3}%
}%
}{#6}{#7}{#8}%
}%
}%
\makeatother
\parindent=-1.5cm
\begin{document}
\begin{tabular}{ll}
\verb|\fib{20}|:&\fib{20}\\
\verb|\fib[]{20}|:&\fib[]{20}\\
\verb|\fib[20]{20}|:&\fib[20]{20}\\
\\
\verb|\fib[1]{10}|:&\fib[1]{10}\\
\\
\verb|\fib[-10]{10}|:&\fib[-10]{10}\\
\verb|\fib[0]{10}|:&\fib[0]{10}\\
\verb|\fib[10]{20}|:&\fib[10]{20}\\
\verb|\fib[5]{10}|:&\fib[5]{10}\\
\verb|\fib[-8]{-8}|:&\fib[-8]{-8}\\
\verb|\fib[-8]{-4}|:&\fib[-8]{-4}\\
\verb|\fib[-1]{0}|:&\fib[-1]{0}\\
\verb|\fib{-1}|:&\fib{-1}\\
\verb|\fib[-1]{-1}|:&\fib[-1]{-1}\\
\verb|\fib[]{-4}|:&\fib[]{-4}\\
\verb|\fib[0]{0}|:&\fib[0]{0}\\
\verb|\fib{0}|:&\fib{0}\\
\verb|\fib{1}|:&\fib{1}\\
\verb|\fib[0]{1}|:&\fib[0]{1}\\
\verb|\fib[1]{1}|:&\fib[1]{1}\\
\\
\verb|\fib[-4]{-8}|:&\fib[-4]{-8}\\
\verb|\fib[3]{0}|:&\fib[3]{0}\\
\verb|\fib[8]{3}|:&\fib[8]{3}\\
\verb|\fib[3]{-8}|:&\fib[3]{-8}\\
\end{tabular}
\end{document}