TeX Alchemist Online

TeX のこと,フォントのこと,Mac のこと

TeX言語で foreach する (2)

前記事で実装した \FOREACH には,次のような欠点がありました。

欠点

この実装の場合,ループの実装に \loop ~ \repeat を使っているので,ネスト(多重ループ)ができません。{\loop ~ \repeat} とブレースでくくればネスト可能な実装にも変更できますが,すると「中身をグルーピングしない」という長所が失われてしまうので,そこは一長一短と言えるでしょう。

そこで,この欠点を改善することを試みます。

というわけで,ifthen パッケージの \whiledo のあたりを使えば,ネストにも問題なく対応できます。

qiita.com

仕様

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}

【出力】

f:id:doraTeX:20220414235121p:plain:w100

このように,無事に多重ループに対応できました!

グルーピングがなされず,外側の変数に対する操作が反映されるか確認しておきます。

\@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 回加算が実行されているはずなので,正しい結果になっていますね!