前記事で実装した \FOREACH
には,次のような欠点がありました。
欠点
この実装の場合,ループの実装に \loop ~ \repeat を使っているので,ネスト(多重ループ)ができません。{\loop ~ \repeat} とブレースでくくればネスト可能な実装にも変更できますが,すると「中身をグルーピングしない」という長所が失われてしまうので,そこは一長一短と言えるでしょう。
そこで,この欠点を改善することを試みます。
「\loop~\repeat をTeX on LaTeXで使う人」
— 某ZR(ざんねん🙃) (@zr_tex8r) 2022年4月14日
は割と多くいる感じなので、だからこそ逆に
「\loop~\repeatをパッケージの実装コードで使うのは避けるべき」
だったりするんですよね。以前にscsnowmanとかいう(素敵☺な)パッケージで問題になった🙃#TeX #TeX言語 https://t.co/qFTIkwa7vI
というわけで,ifthen
パッケージの \whiledo
のあたりを使えば,ネストにも問題なく対応できます。
仕様
pgffor
パッケージの \foreach
と同様に,
\FOREACH \i in {添字集合} {繰り返し内容}
の仕様で使えるようにしましょう。ただし,添字集合の部分は前記事と同様に
{1,2,5-10,20-15,4}
のような指定を受け付けるものとします。
また,{繰り返し内容}
の部分をグルーピングをせず,\FOREACH
が存在するスコープの変数に対する局所化された操作(代入やカウンタ演算)を反映させるようにします。
実装
実装のアイデアとしては,「for
ループの何重目のネストか」というのをカウンタで保持しておき,その値に応じて各ネスト階層ごとに別々の内部変数を用意することで,外側と内側の内部変数の名前を衝突させないようにして,多重ループに対応させることとしましょう。グルーピングをしたくないので,「for
ループの何重目のネストか」というカウンタ値は手動で上げ下げします。
【入力】
\documentclass{article} \usepackage{ifthen} \makeatletter \newcount\FOREACH@depth \FOREACH@depth=\z@ \long\def\FOREACH#1in#2#3{% \advance\FOREACH@depth\@ne \@namedef{FOREACH@var\the\FOREACH@depth}{#1}% \@namedef{FOREACH@body\the\FOREACH@depth}{#3}% \@FOREACH{#2}% \advance\FOREACH@depth\m@ne } \def\@FOREACH#1{\@@FOREACH#1,\@empty\@nil} \def\@@FOREACH#1,#2#3\@nil{% \@@@FOREACH#1-#1-\@nil \unless\ifx#2\@empty \@FOREACH{#2#3}% \fi } \def\@@@FOREACH#1-#2-#3\@nil{\@@@@FOREACH{#1}{#2}} \def\@@@@FOREACH#1#2{% \def\FOREACH@start{\csname FOREACH@start\the\FOREACH@depth\endcsname}% \def\FOREACH@goal{\csname FOREACH@goal\the\FOREACH@depth\endcsname}% \def\FOREACH@var{\csname FOREACH@var\the\FOREACH@depth\endcsname}% \def\FOREACH@body{\csname FOREACH@body\the\FOREACH@depth\endcsname}% \expandafter\expandafter\expandafter\newcount\FOREACH@start \expandafter\expandafter\expandafter\newcount\FOREACH@goal \FOREACH@start=#1 \FOREACH@goal=#2 \def\FOREACH@loop{% \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\edef\FOREACH@var{\the\FOREACH@start}% \FOREACH@body }% \ifnum\FOREACH@start<\FOREACH@goal \whiledo{\NOT{\FOREACH@start>\FOREACH@goal}}{% \FOREACH@loop \advance\FOREACH@start\@ne }% \else \whiledo{\NOT{\FOREACH@start<\FOREACH@goal}}{% \FOREACH@loop \advance\FOREACH@start\m@ne }% \fi } \makeatother \begin{document} \FOREACH \x in {1-3} {% 連続 \FOREACH \y in {2,4,6} {% 離散 \FOREACH \z in {10,5-3} {% 混合,逆順 (\x,\y,\z) \par }% }% } \end{document}
【出力】
このように,無事に多重ループに対応できました!
グルーピングがなされず,外側の変数に対する操作が反映されるか確認しておきます。
\@tempcnta=0 \FOREACH \x in {1-3} {% \FOREACH \y in {2,4,6} {% \FOREACH \z in {10,5-3} {% \advance\@tempcnta\@ne }% }% }% final value = \the\@tempcnta % => 36
3重ループの結果,3×3×4=36 回加算が実行されているはずなので,正しい結果になっていますね!