多くのプログラミング言語で,(疑似コードでいうところの)
for i in 1...10 do { print(i) }
のような foreach
文があります。これを同様の繰り返しを LaTeX で実現するにはどうすればよいでしょうか。
pgffor
パッケージの \foreach
TeX Live に標準で含まれる pgffor
パッケージをロードすることで,自由自在な \foreach
が使えるようになります。
1,...,5
のように書けば公差1の等差数列1,3,...,11
のように最初の二項を明示すれば任意の公差の等差数列1,3,10,19
のように書けば任意に与えられた具体的な数列- 2.と3.の記法を連結したもの
に関して,繰り返し処理を実行できます。
サンプル
\documentclass{article} \usepackage{pgffor} \begin{document} % 公差1の等差数列 \foreach \i in {1,...,5} {[\i]} % => [1][2][3][4][5] % 公差3の等差数列 \foreach \i in {4,7,...,19} {[\i]} % => [4][7][10][13][16][19] % 公差-2の等差数列 \foreach \i in {7,5,...,1} {[\i]} % => [7][5][3][1] % 任意に与えられる数列 \foreach \i in {4,9,1,3,10,11} {[\i]} % => [4][9][1][3][10][11] % 任意に与えられる数列と,部分的に等差数列の合併 \foreach \i in {4,8,3,5,...,11,20,17,...,8,0} {[\i]} % => [4][8][3][5][7][9][11][20][17][14][11][8][0] \end{document}
pgffor
パッケージの不満点(というほどではないけれども)
この pgffor
パッケージの \foreach
がとても強力なので,これで十分事足りるのですが,強いて不満点を挙げるとするならば,次のような点でしょうか。
,...,
という部分のタイプ数が多い。\foreach
の内容部がグルーピングされる。
2点目は,たとえば次のようなことを指しています。
【入力】
\@tempcnta=0 \foreach \i in {1,...,5} {% current value = \the\@tempcnta\par \advance\@tempcnta\i }% final value = \the\@tempcnta\par
【出力】
current value = 0 current value = 0 current value = 0 current value = 0 current value = 0 final value = 0
普通のプログラミング言語の for
ループの感覚で使っていると,この結果に当惑してしまうことでしょう。
これは,\foreach
の内容部がグルーピングされているため,局所的な変数操作の結果はループごとにリセットされてしまうからです。
もちろん,\advance
のところに \global
を付けておけば大域的に変数の値を操作でき,結果を次のループに持ち越せるのですが,そうすると「スコープの一番外側」まで変数操作が波及してしまうことになり,思わぬ悪影響を及ぼす危険性があります。
自分で foreach
を作ってみる
そこで,TeX言語の練習として,同じような foreach
文を,異なる書式で作ってみましょう。
サンプルソース
\documentclass{article} \makeatletter \long\def\FOREACH#1#2{% \def\FOREACH@BODY##1{#2}% \@FOREACH{#1}% } \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}} \newcount\FOREACH@start \newcount\FOREACH@goal \def\@@@@FOREACH#1#2{% \FOREACH@start=#1 \FOREACH@goal=#2 \ifnum\FOREACH@start<\FOREACH@goal \loop \FOREACH@BODY{\the\FOREACH@start}% \advance\FOREACH@start\@ne \unless\ifnum\FOREACH@start>\FOREACH@goal\repeat \else \loop \FOREACH@BODY{\the\FOREACH@start}% \advance\FOREACH@start\m@ne \unless\ifnum\FOREACH@start<\FOREACH@goal\repeat \fi } \makeatother \begin{document} % 単独 \FOREACH{1}{[#1]} % => [1] % 増加列 \FOREACH{1-5}{[#1]} % => [1][2][3][4][5] % 減少列 \FOREACH{5-1}{[#1]} % => [5][4][3][2][1] % 任意の数列 \FOREACH{1,5,10,11}{[#1]} % => [1][5][10][11] % これらの合併 \FOREACH{1,5-9,20-17,2-2}{[#1]} % => [1][5][6][7][8][9][20][19][18][17][2] \end{document}
仕様
上記サンプルのように,
\FOREACH{添字集合}{繰り返し内容}
の書式で使い,{繰り返し内容}
の部分ではループカウンタ変数を #1
で参照できます。
pgffor
の \foreach
とは異なり,対応できる公差は ±1 だけですが,その場合に関しては添字集合が ,...,
より簡潔に -
で書けます。
(負の数を使いたいときは,{-1}
のようにブレースで囲んでおけば大丈夫です。)
また,{繰り返し内容}
が強制グルーピングされないので,次のような結果になります。
【入力】
\@tempcnta=0 \FOREACH{1-5}{% current value = \the\@tempcnta\par \advance\@tempcnta#1 }% final value = \the\@tempcnta
【出力】
current value = 0 current value = 1 current value = 3 current value = 6 current value = 10 final value = 15
これで普通のプログラミング言語の for
ループと同じ感覚で使えますね!
マクロの展開を追いかける
ここで定義した \FOREACH
は,次のようにマクロを展開することで添字集合部分のパースを実現しています。
単独の場合
\@FOREACH{10} ⇒ \@@FOREACH10,\@empty\@nil ⇒ \@@@FOREACH10-10-\@nil ⇒ \@@@@FOREACH{10}{10}
連続の場合
\@FOREACH{1-5} ⇒ \@@FOREACH1-5,\@empty\@nil ⇒ \@@@FOREACH1-5-1-5-\@nil ⇒ \@@@@FOREACH{1}{5}
離散の場合
\@FOREACH{1,9} ⇒ \@@FOREACH1,9,\@empty\@nil ⇒ \@@@FOREACH1-1-\@nil + \@FOREACH{9,\@empty}
前者は
\@@@FOREACH1-1-\@nil ⇒ \@@@@FOREACH{1}{1}
と展開され,後者は
\@FOREACH{9,\@empty} \@@FOREACH9,\@empty,\@empty\@nil ⇒ \@@@FOREACH9-9-\@nil ⇒ \@@@@FOREACH{9}{9}
と展開されます。
混合の場合
\@FOREACH{1-5,8,10-12} ⇒ \@@FOREACH1-5,8,10-12,\@empty\@nil ⇒ \@@@FOREACH1-5-1-5-\@nil + \@FOREACH{8,10-12,\@empty}
前者は
\@@@FOREACH1-5-1-5-\@nil ⇒ \@@@@FOREACH{1}{5}
と展開され,後者は
\@FOREACH{8,10-12,\@empty} ⇒ \@@FOREACH8,10-12,\@empty,\@empty\@nil ⇒ \@@@FOREACH8-8-\@nil + \@FOREACH{10-12,\@empty}
と展開されます。以下,これを再帰的に繰り返します。
以上を繰り返して,最終的に添字集合部分のパースが済むと,いずれも
\@@@@FOREACH{開始}{終了}
の形に展開されるので,後はこれを実装すればよいわけです。繰り返しの内容部は,はじめに \FOREACH@BODY
という命令に収めて退避してありますので,これを呼び出せばOKです。
欠点
この実装の場合,ループの実装に \loop ~ \repeat
を使っているので,ネスト(多重ループ)ができません。{\loop ~ \repeat}
とブレースでくくればネスト可能な実装にも変更できますが,すると「中身をグルーピングしない」という長所が失われてしまうので,そこは一長一短と言えるでしょう。
この欠点の解決は次の記事で。