Re: Fast expanding macros /Was: How to declare recursive factorial function?
- From: Ulrich Diez <eu_angelion@xxxxxxxxxxxxxx>
- Date: Fri, 14 Mar 2008 21:47:21 +0100
mpg wrote:
By the way, I've been thinking a moment about the following: given a macro[...]
that works purely by expansion, in which cases is it possible to make it
deliver its result within two expansion steps, and how?
2. The macro is a test which expands either one or the other of its
arguments: one can use \romannumral0[...]\space where the [...] part
finally triggers an argument grabber like \@firstoftwo or so on.
There is plenty of stuff where expansion is not suppressed in
order to obtain arguments, values or whatever.
E.g., expansion takes place when looking for opening-braces or
key-words related to things like register-assignments or
\unexpanded or \scantokens, with those \if-comparisons, with
\csname, with \the, when gathering a numerical value, at some
places within alignments...
For some obscure reason I like \romannumeral.
This can also be used for argument-list-iterating.
Is there any other tricks like that?
For example, I thought a lot about the following problem. Given a list of,
say coma-separated items, and a purely expandable macro comparing two items
and returning the "greatest" (in some sense), it is possible to compute
the "maximum" of the list purely by expansion. But is it possible to make
the macro return a result in two expansion-steps? Assume it is not a list
of number, so we cannot use the a \number trick, and it has more than 9
elements, so I cannot see how to use argument grabbers. Is it still
possible? Any clue?
In order to keep iteration in progress, you can still use the
\romannumeral0<space>-trick - \romannumeral0<space> doesn't
deliver any token, but you can "fool" \romannumeral into
expanding a lot of stuff (e.g., some iterator-macro) and moving
around a few macro-arguments before finding that <space> at
the time when iteration should terminate.
E.g., create an iterator for the list which besides the list
takes some arguments used for action after recursion.
Let the iterator modify these arguments during iteration...
e.g. \iterate{foo}{elem1,elem2,...}
next step: \iterate{baz}{elem2,...}
....while not delivering any token "outside" the iterator's
"argument-grabbing-range".
At the end of the recursion, have the iterator return the
modified argument.
You can probably put such a thing behind \romannumeral0 and have
delivered a space at the end of the recursion, right before
having the iterator return the modified argument.
This yields expandability in two steps.
The following example defines a "generic macro" \loopcall which
can be used for iterating on argument-lists.
As an example it also delivers the macro \findlargestpositive
number which internally uses \loopcall.
It takes an argument-list consisting of numbers and delivers
the token which yields the largest value.
I think, the gist of the whole thing is probably the macro
\togglelargernumber - it "touches"/modifies the arguments
which will be passed to \loopcall in the next iteration.
But comma-list-parsing is poorly implemented (e.g., no lead-and-
trail-space-checking, also no check for a missing comma at the
end of the list.)
Actually I don't like comma-lists all too much any more and
prefer "argument-lists" wherever possible because with comma-
lists you always need to decide upon the treatment of "edge-
cases", e.g. consecutive commas, trailing-comma, leading-comma,
only blank-stuff between comma, how many levels of brace-
stripping, catcode-change of comma, ...
I assume, somebody might easily bring up a snippet of code which
does the same things within only a few lines...
If you are interested in such stuff, have a look at the
TeX pearl divers site at http://www-stary.gust.org.pl/pearls/
(-> Pearls 2005 -> David Kastrup: Iterating with roman numeral).
I also learned a lot when Robin Fairbairns pointed me to the
'Around the bend'-department at
http://www.ctan.org/tex-archive/info/aro-bend/.
There you find the text-files exercise.001 - exercise.018
and answer.001 - answer.018.
Sincerely
Ulrich
\documentclass{article}
\makeatletter
%
% A "reliable" expandable empty-test without e-TeX is feasible
% but it requires much more code. (The remaining code is
% without e-TeX.)
%
\newcommand\my@ifempty[1]{%
\expandafter\ifx\expandafter\relax\detokenize{#1}\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
%
% Auxiliary macros to \extractfirstargument:
%
\newcommand\switch[2]{{#2}{#1}}%
\@ifdefinable\@carbrace{%
\long\def\@carbrace#1#2\@nil{{#1}}%
}
\newcommand\@extractfirstargumentloop[4]{%
\expandafter\my@ifempty\expandafter{\@gobble#1}%
{\endcsname{#3}#1#4{#2}}%
{%
\expandafter\@extractfirstargumentloop
\expandafter{%
\@carbrace#1}{#2}{#3}{#4}%
}%
}
%
% \extractfirstargument{<List-Action>}%
% {<Empty-Action>}%
% {<Args>}%
% {{Elem1}{Elem2}..{ElemN}}
% yields in case Elem-List is not empty:
% <Action>{Elem1}<Args>{{Elem2}..{ElemN}}
% yields in case Elem-List is empty:
% <Empty-Action><Args>
%
\newcommand\extractfirstargument[4]{%
\csname @firstofone%
\my@ifempty{#4}%
{\endcsname{#2}#3}%
{%
\expandafter\expandafter
\expandafter \@extractfirstargumentloop
\expandafter\switch
\expandafter{%
\@gobble#4}{#4\@nil}{#1}{#3}%
}%
}
%
% Auxiliary macros to \commaextractfirstargument
%
\long\def\@gobblecomma#1,{}%
\@ifdefinable\@carbracecomma{%
\long\def\@carbracecomma#1,#2\@nil,{#1,}%
}
\@ifdefinable\@stripcomma{%
\long\def\@stripcomma#1,{#1}%
}
\newcommand\@commaextractfirstargumentloop[4]{%
\expandafter\my@ifempty\expandafter{\@gobblecomma#1}%
{\expandafter\expandafter
\expandafter \expandafter
\expandafter\expandafter
\expandafter \endcsname
\expandafter\expandafter
\expandafter \switch
\expandafter\expandafter
\expandafter {%
\expandafter\@gobble
\@stripcomma#1}{#3}#4{#2}%
}{%
\expandafter\@commaextractfirstargumentloop
\expandafter{\@carbracecomma#1}{#2}{#3}{#4}%
}%
}
%
% \commaextractfirstargument{<List-Action>}%
% {<Empty-Action>}%
% {<Args>}%
% {Elem1,Elem2,..ElemN,}
% % Obey the trailing comma!
% % No surrounding-space-treatment!
% yields in case Elem-List is not empty:
% <Action>{Elem1}<Args>{Elem2,..ElemN,}
% yields in case Elem-List is empty:
% <Empty-Action><Args>
%
\newcommand\commaextractfirstargument[4]{%
\csname @firstofone%
\my@ifempty{#4}%
{\endcsname{#2}#3}%
{%
\expandafter\expandafter
\expandafter \@commaextractfirstargumentloop
\expandafter\switch
\expandafter{%
\@gobblecomma#4}{.#4\@nil,}{#1}{#3}%
}%
}
%
% \loopcall{<List-Action>}%
% {<Empty-Action>}%
% {<Args>}%
% {{Elem1}{Elem2}..{ElemN}}
% performs in case Elem-List is not empty:
% <Action>{Elem1}<Args>{{Elem2}..{ElemN}}%
% performs in case Elem-List is not empty:
% <Empty-Action><Args>
% That means it delivers:
% <Action>{Elem1}<Args>
% <Action>{Elem2}<Args>
% ...
% <Action>{ElemN}<Args>
% <Empty-Action><Args>
%
\newcommand\loopcall[4]{%
\my@ifempty{#4}{%
\extractfirstargument{#1}{#2}{#3}{#4}%
}{%
\extractfirstargument{#1}{#2}{#3\loopcall{#1}{#2}{#3}}{#4}%
}%
}
%%
%%
\newcommand*\togglelargernumber{}
%
% <Action>'s first arg is the list-element
% <Action>'s second arg is whatever you pass as <Args>, e.g.
% keeps track of the largest number in the list.
% Define <Action> to modify the look of the 3rd Argument of the
% next call to \loopcall according to its own first argument:
%
% The gist is: You can do whatever you want with
% \loopcall's <Args>.
% You are not bound to numerical comparisons. Also you are not
% bound to only two forking-routes. You can stop iterating
% completely by gobbling the remaining list. You can switch to
% another iterator...
% You can even have more than 9 args in <Args> and call in an
% interim-step yet a few other macros in order to process them
% first...
%
\long\def\togglelargernumber#1#2\loopcall#3#4#5{%
\ifnum0#2>0#1
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
{\loopcall{#3}{#4}{#2}}%
{\loopcall{#3}{#4}{#1}}%
}
%
\newcommand\printlargestnumber[1]{ #1}% space terminates
% % \romannumeral
\newcommand\findlargestpositivenumber[1]{%
\romannumeral0\loopcall{\togglelargernumber}%
{\printlargestnumber}%
{{}}%
{#1}%
}
\begin{document}
\edef\TESTxA{%
\findlargestpositivenumber{%
{0}{4}{16}{8}{4}{3}{55}{11}{27}{46}%
}%
}
\message{\string\TESTxA: \meaning\TESTxA^^J}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\TESTxB
\expandafter\expandafter\expandafter{%
\findlargestpositivenumber{%
{0}{4}{16}{8}{4}{3}{55}{11}{27}{46}%
}%
}
\message{\string\TESTxB: \meaning\TESTxB^^J}
\begingroup
\let\extractfirstargument=\commaextractfirstargument
\edef\TESTxC{%
\findlargestpositivenumber{0,4,16,8,4,3,55,11,27,46,}%
}
\message{\string\TESTxC: \meaning\TESTxC^^J}
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\TESTxD
\expandafter\expandafter\expandafter{%
\findlargestpositivenumber{0,4,16,8,4,3,55,11,27,46,}%
}
\message{\string\TESTxD: \meaning\TESTxD^^J}
\endgroup
\let\@nil=\relax
\edef\TESTxE{%
\extractfirstargument{Action}{Empty}%
{{1}{2}{3}{4}{5}{6}}%
{\@nil\@nil{A}{B}{C}\@nil{D}{E}\@nil\@nil}%
}
\message{\string\TESTxE: \meaning\TESTxE^^J}
\edef\TESTxF{%
\extractfirstargument{Action}{Empty}%
{{1}{2}{3}{4}{5}{6}}%
{{A}{B}{C}{D}{E}}%
}
\message{\string\TESTxF: \meaning\TESTxF^^J}
\edef\TESTxG{%
\extractfirstargument{Action}{Empty}%
{{1}{2}{3}{4}{5}{6}}%
{{A}}%
}
\message{\string\TESTxG: \meaning\TESTxG^^J}
\edef\TESTxH{%
\extractfirstargument{Action}{Empty}%
{{1}{2}{3}{4}{5}{6}}%
{}%
}
\message{\string\TESTxH: \meaning\TESTxH^^J}
\newcommand\Action[7]{%
\message{^^J^^J1st:#1^^J2nd:#2^^J3rd:#3^^J4th:#4^^J%
5th:#5^^J6th:#6^^J7th:#7^^J}%
}%
\newcommand\EmptyAct[6]{%
\message{^^J^^JE-1st:#1^^JE-2nd:#2^^JE-3rd:#3^^JE-4th:#4^^J%
E-5th:#5^^JE-6th:#6^^J}%
}%
\loopcall{\Action}{\EmptyAct}%
{{1}{2}{3}{4}{5}{6}}%
{\@nil\@nil{A}{B}{C}{Blebb}{D}{E}\@nil}
\edef\TESTxI{%
\loopcall{Action}{Empty}%
{{1}{2}{3}{4}{5}{6}}%
{{{A}}{{B}}{{C}}\@nil{{D}}{{E}}\@nil\@nil}%
}
\message{\string\TESTxI: \meaning\TESTxI^^J}
\end{document}
.
- Follow-Ups:
- References:
- How to declare recursive factorial function?
- From: Spectrum (Claus Appel)
- Re: How to declare recursive factorial function?
- From: Ulrich Diez
- Re: Fast expanding macros /Was: How to declare recursive factorial function?
- From: mpg
- How to declare recursive factorial function?
- Prev by Date: change font colours in nav bars | beamer
- Next by Date: Re: Fast expanding macros /Was: How to declare recursive factorial function?
- Previous by thread: Re: Fast expanding macros /Was: How to declare recursive factorial function?
- Next by thread: Re: Fast expanding macros /Was: How to declare recursive factorial function?
- Index(es):
Relevant Pages
|