出題の 3 つの関数の実装
前回に作った補助関数 \zrxxpz_tree_dispatch:NNn を用いて、問題で求められている 3 つの関数(\sumLeaves、\treeDepth、\sumLeavesWithin*1)を実装することにする。前回のコードと同じ部分がかなりあるが敢えて完全なソースリストを掲載する。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn %------------------------
%%<*> \sumLeaves {<木>}
\cs_new:Nn \zrxxpz_sum_leaves:n {
\int_value:w \int_eval:w 0
\zrxxpz_sum_leaves_aux:n {#1}
\int_eval_end:
}
\cs_new:Npn \zrxxpz_sum_leaves_aux:n {
\zrxxpz_tree_dispatch:NNn
\zrxxpz_sum_leaves_at_leaf:n \zrxxpz_sum_leaves_at_node:nn
}
\cs_new:Nn \zrxxpz_sum_leaves_at_leaf:n {
+ #1
}
\cs_new:Nn \zrxxpz_sum_leaves_at_node:nn {
\zrxxpz_sum_leaves_aux:n {#1}
\zrxxpz_sum_leaves_aux:n {#2}
}
\cs_new_eq:NN \sumLeaves \zrxxpz_sum_leaves:n
%%<*> \treeDepth {<木>}
\cs_new:Nn \zrxxpz_tree_depth:n {
\int_value:w \int_eval:w
\zrxxpz_tree_depth_aux:n {#1}
\int_eval_end:
}
\cs_new:Npn \zrxxpz_tree_depth_aux:n {
\zrxxpz_tree_dispatch:NNn
\zrxxpz_tree_depth_at_leaf:n \zrxxpz_tree_depth_at_node:nn
}
\cs_new:Nn \zrxxpz_tree_depth_at_leaf:n {
\c_zero
}
\cs_new:Nn \zrxxpz_tree_depth_at_node:nn {
\zrxxpz_int_max:nn
{ \zrxxpz_tree_depth_aux:n {#1} }
{ \zrxxpz_tree_depth_aux:n {#2} }
+ 1
}
\cs_new_eq:NN \treeDepth \zrxxpz_tree_depth:n
%% \zrxxpz_int_max:nn {<整数式1>} {<整数式2>}
% 式の評価を一度しかしない \int_max:nn.
\cs_new:Nn \zrxxpz_int_max:nn {
\exp_args:Noo \int_max:nn
{ \int_value:w \int_eval:w #1 \int_eval_end: }
{ \int_value:w \int_eval:w #2 \int_eval_end: }
}
%%<*> \sumLeavesWithin {<木>} {<深さ>}
\cs_new:Nn \zrxxpz_sum_leaves_within:nn {
\int_value:w \int_eval:w 0
\zrxxpz_sum_leaves_within_aux:nn {#1} {#2}
\int_eval_end:
}
\cs_new:Npn \zrxxpz_sum_leaves_within_aux:nn { % {<木>}{<深さ>}
% tree_dispatch は次の引数の <木> までを処理し,
% その次の <深さ> には手を付けない.
% 従って, <深さ> は sum_leaves_within_at_leaf/node に
% そのまま渡される.
\zrxxpz_tree_dispatch:NNn
\zrxxpz_sum_leaves_within_at_leaf:nn
\zrxxpz_sum_leaves_within_at_node:nnn
}
\cs_new:Nn \zrxxpz_sum_leaves_within_at_leaf:nn { % {<ラベル>} {<深さ>}
+ #1
}
\cs_new:Nn \zrxxpz_sum_leaves_within_at_node:nnn { % {<左>} {<右>} {<深さ>}
\int_compare:nNnT {#3} > { 0 } {
\exp_args:Nnno \zrxxpz_sum_leaves_within_at_node_aux:nnn {#1} {#2}
{ \int_value:w \int_eval:w #3 - 1 \int_eval_end: }
}
}
\cs_new:Nn \zrxxpz_sum_leaves_within_at_node_aux:nnn {
\zrxxpz_sum_leaves_within_aux:nn {#1} {#3}
\zrxxpz_sum_leaves_within_aux:nn {#2} {#3}
}
\cs_new_eq:NN \sumLeavesWithin \zrxxpz_sum_leaves_within:nn
%%<*> \sumLeavesWithinOne {<木>}
% \sumLeavesWithin で第 2 引数を 1 に固定したもの.
\cs_new:Npn \sumLeavesWithinOne #1 {
\sumLeavesWithin {#1} { 1 }
}
%% \testSumLeavesWithin {<木>}
% 与えられた木に対して, 深さ 0, 1, 2, 3, 4 の各々に対して
% \sumLeavesWithin を適用した結果を順に端末に表示する.
\NewDocumentCommand \testSumLeavesWithin { m } {
\cs_set:Nn \zrxxpz_test_sum_leaves_within_aux:n {
\sumLeavesWithin {#1} {##1}
;
}
\iow_term:x {
\prg_stepwise_function:nnnN { 0 } { 1 } { 4 }
\zrxxpz_test_sum_leaves_within_aux:n
}
}
\cs_new:Nn \zrxxpz_test_sum_leaves_within_aux:n {}
%%%% ↓以下は前回までに示したコードと同じ↓
%% \zrxxpz_tree_dispatch:NNn \関数L \関数N {<二分木>}
% 葉ならば \関数L{<ラベル>}, 内部点なら \関数N{<左>}{<右>} に展開される.
\cs_new:Npn \zrxxpz_tree_dispatch:NNn #1#2#3 {
\zrxxpz_tree_dispatch_aux_i:w #3 \q_stop
#1#2
}
\cs_new:Npn \zrxxpz_tree_dispatch_aux_i:w #1 \q_stop {
\zrxxpz_tree_dispatch_aux_ii:ww #1 | \q_stop
}
\cs_new:Npn \zrxxpz_tree_dispatch_aux_ii:ww #1 | #2 \q_stop {
\tl_if_empty:nTF {#2}
{ \zrxxpz_tree_dispatch_aux_iii:nNN {#1} }
{ \zrxxpz_tree_dispatch_aux_iv:nwNN {#1} #2 }
}
\cs_new:Nn \zrxxpz_tree_dispatch_aux_iii:nNN {
#2 {#1}
}
\cs_new:Npn \zrxxpz_tree_dispatch_aux_iv:nwNN #1#2 | #3#4 {
#4 {#1} {#2}
}
%%<*> \doAllTest \命令
% \do を \命令 と等価にして, \testList を完全展開した結果を表示する.
\tl_new:N \zrxxpz_result_tl
\NewDocumentCommand \doAllTest { m } {
\cs_set_eq:NN \do #1
\iow_term:x { \testList }
}
%%<*> \testList: テストデータ(の集まり)
\tl_new:N \testList
\tl_set:Nn \testList {%
\do8 ;
\do{16} ;
\do{8|9} ;
\do{{8|9}} ;
\do{{2|3}|5} ;
\do{2|{4|5}} ;
\do{{2|3}|{4|5}} ;
\do{{{2|3}|{4|5}}} ;
\do{{{{9|2}|6}|{4|{15|{2|4}}}}} ;
}
\ExplSyntaxOff %------------------------
% テスト実行(\doAllTest)
\doAllTest\sumLeaves
\doAllTest\treeDepth
\doAllTest\sumLeavesWithinOne
% テスト実行(\testSumLeavesWithin)
\testSumLeavesWithin{{{{9|2}|6}|{4|{15|{2|4}}}}}
\begin{document}
\end{document}テストでは、以下の結果を端末に出力させている。
\testListの各々の木 T について、\sumLeaves{T}の値。\testListの各々の木 T について、\treeDepth{T}の値。\testListの各々の木 T について、\sumLeavesWithin{T}{1}の値。- 木 T =
{{{9|2}|6}|{4|{15|{2|4}}}}と深さ d = 0, 1, 2, 3, 4 について、\sumLeavesWithin{T}{d}の値。
なお、端末への出力には \iow_term:x という標準関数を用いている。指定子 x から判るように、引数のトークン列は網羅展開される(その過程で \sumLeaves 等も網羅展開されている)。ちなみに、\iow_term:x は LaTeX2e の \typeout(これも引数を網羅展開するのであった)と機能的に同値であるが、expl3 には「展開しない」版の \iow_term:n も存在する。
出力結果は以下の通り。
8;16;17;17;10;11;14;14;42; 0;0;1;1;2;2;2;2;4; 8;16;17;17;5;2;0;0;0; 0;0;10;36;42;
実装のポイント
上掲の 3 つの関数(の本体)の実装はどれも以下のような形になっている。
\int_value:w \int_eval:w <整数式> \int_eval_end:
これを展開すると、与えられた式の値(の十進表記)に展開される。(ちなみに生の TeX で書くと \number\numexpr<整数式>\relax である。)この「式」の部分には式(の一部)に展開されるような関数を置くことも可能であり、従って、「適当な式文字列」を生成する展開可能関数(制限付展開可能でも可)を構成すればよい。例えば、\sumLeaves では「式」は 0 \zrxxpz_sum_leaves_aux:n {<木>} である。この \zrxxpz_sum_leaves_aux:n は実は以下のような単純なことしかしていないが、これで所望の結果が得られるのである。
\zrxxpz_sum_leaves_aux:n {{{9|2}|6}|{4|{15|{2|4}}}}
→ +9+2+6+4+15+2+4なお、上掲のテストでは制限付展開可能性しか検査していない(網羅展開しているので)が、実際には完全展開可能である。上記の \int_value:w 〜 の構成では、先頭の \int_value:w を 1 回展開するとそれだけで最後の \int_eval_end: を見つけるまで途中にある展開可能トークンを展開し続けるからである。*2(複数の展開が行われるがそれが全体で「1 回の展開」と見做されることに注意。)\sumLeaves の定義本体の先頭が \int_value:w であるので、これから \sumLeaves が「完全展開可能」であることが導かれる。
関数 \zrxxpz_int_max:nn について
小問 2 の \treeDepth では途中で「2 つの整数の最大値」を求める箇所がある。この機能を与える標準関数 \int_max:nn は完全展開可能であるのでこれを使えそうにみえる。ところが、この関数には、引数の整数式を 2 回評価するという欠点がある。*3普通はそれでもあまり問題にならないだろうが、この問題の場合、引数が \zrxxpz_tree_depth_aux:n の再帰呼出になっているので、2 回評価されると不必要なコストがかかってしまう。これを避けるために、引数を先に評価する版である \zrxxpz_int_max:nn を定義して用いている。
\cs_new:Nn \zrxxpz_int_max:nn {
\exp_args:Noo \int_max:nn
{ \int_value:w \int_eval:w #1 \int_eval_end: }
{ \int_value:w \int_eval:w #2 \int_eval_end: }
}見ての通り、与えられた整数式を先述の \int_value:w 〜 の構成の中に入れて、\exp_args:〜 を利用してそれを 1 回展開してから、標準の \int_max:nn にその結果(単なる数字になっている)を渡している。「1 回展開」で十分な理由も既に述べた。
まとめ
expl3 の枠組でも、関数を完全展開可能にするのは極めて大変である。
従って、expl3 の初級者は関数を完全展開可能にすることに拘ってはいけない。絶対。
*「えー!? またそれですかー? というか、お前も(expl3 の)初級者ではないのかと小一時間\<(ry」