今回は,\if
系トークンでの判定でよく使われるテクニック3選として,次の3つの技巧を紹介しましょう。
\if
系トークンでの判定でOR
条件\if
系トークンでの判定でAND
条件- 空文字列判定
【目次】
LaTeXの ifthen
パッケージとの比較
このような条件判定を行うには,LaTeXの ifthen
パッケージが便利です。ifthen
パッケージについては,次の記事にまとめられています。
ifthen
パッケージを使えば,OR
, AND
, 空文字列判定は次のように容易に実現できます。(ここでは,\newcounter{a}
などとしてLaTeX式カウンタの変数が定義されているものとします。)
OR
条件
% a==42 OR b<99 の判定 \ifthenelse{\value{a}=42\OR\value{b}<99}{TRUE}{FALSE}
AND
条件
% a==42 AND b<99 の判定 \ifthenelse{\value{a}=42\AND\value{b}<99}{TRUE}{FALSE}
空文字列判定
\newcommand\hoge[1]{% \ifthenelse{\equal{#1}{}}{% EMPTY% }{% [#1]% }% }
ifthen
パッケージの欠点
このように,ifthen
パッケージを使うと条件判定が楽なのですが,欠点として,完全展開できない,すなわち \edef
の中で使えないという点が挙げられます。つまり,\edef
による定義文の中に \ifthenelse
による条件分岐は含められないのです。
一方,\if
系トークンでの条件判定は,完全展開可能という点が強みです。ただしその場合,OR
などの実現にトリッキーな技巧が必要になります。
テク①:\if
系トークンでの判定でOR
条件
イメージとしてはビットフラグみたいな感じです。例えば,\value{a}=42
OR \value{b}<99
であれば,次のように実現します。
% a==42 OR b<99 の判定 \ifnum0\ifnum\value{a}=42 1\fi\ifnum\value{b}<99 1\fi>0 TRUE\else FALSE\fi
両方真のときは \ifnum011>0
,片方のみ真のときは \ifnum01>0
,両方偽のときは \ifnum0>0
となり,少なくとも一方が真のときにのみ TRUE
側が実行されるようになります。\ifnum0
という先頭の 0
は,両方偽のときに 0
という数字を残すために置いているわけです。これがないと,両方偽の場合に \ifnum>0
となってしまって構文エラーとなります。
なお,数値リテラルの終結をTeXインタプリタに伝えるためのスペースが所々に入れてある点に注意しましょう。
もちろん,次のように実現してもよいです。
% a==42 OR b<99 の判定 \ifnum\ifnum\value{a}=42 1\else0\fi\ifnum\value{b}<99 1\else0\fi>0 TRUE\else FALSE\fi
\value{a}=42
かどうかで十の位が 1
か 0
,\value{b}<99
かどうかで一の位が 1
か 0
になり,全パターンで 11>0
, 10>0
, 01>0
, 00>0
の4通りになるので,最後の場合のみ偽扱いになります。この方が本来の「ビットフラグ」感がありますね。
テク②:\if
系トークンでの判定でAND
条件
AND
判定も同様のアイデアでOKです。(TRUE
側だけが欲しいならば単に \if~
をネストすればよいですが,ここでは FALSE
側も必要なケースを想定します。)
% a==42 AND b<99 の判定 \ifnum0\ifnum\value{a}=42 1\fi\ifnum\value{b}<99 1\fi=11 TRUE\else FALSE\fi
この場合,両方真の場合は \ifnum011=11
, 一方のみ真の場合は \ifnum01=11
, 両方偽の場合は \ifnum0=11
となり,両方真の場合にのみ TRUE
側が実行されます。
応用:もっと凝った条件判定
「a=1かつb=2」または「c=3かつd=4」,のような凝った条件判定も,上記の手法をネストさせることで実現できます。
% (a==1 AND b==2) OR (c==3 AND d==4) の判定 \ifnum0% \ifnum0% \ifnum\value{a}=1 1\fi \ifnum\value{b}=2 1\fi =11 1\fi \ifnum0% \ifnum\value{c}=3 1\fi \ifnum\value{d}=4 1\fi =11 1\fi >0 TRUE\else FALSE\fi
あるいは,\numexpr
を用いて,ビット演算するような発想もよいでしょう。
% (a==1 AND b==2) OR (c==3 AND d==4) の判定 \ifnum \numexpr \ifnum \numexpr \ifnum\value{a}=1 1\else0\fi *% \ifnum\value{b}=2 1\else0\fi =1 1\else 0% \fi +% \ifnum \numexpr \ifnum\value{c}=3 1\else0\fi *% \ifnum\value{d}=4 1\else0\fi =1 1\else 0% \fi >0 TRUE\else FALSE\fi
これは,普通のプログラミング言語的な記法で書けば,
if (a==1 ? 1 : 0) * (b==2 ? 1 : 0) + (c==3 ? 1 : 0) * (d==4 ? 1 : 0) > 0 then "TRUE" else "FALSE" end
のようなことをやっています。
テク③:空文字列判定
\def\hoge#1{% \if"#1"% EMPTY% \else [#1]% \fi }
これによって #1
が空文字列かどうかを判定しています。あたかも,\if"#1"
という「空文字列判定構文」があるように見えますが,TeX言語としてそういう構文があるわけではなく,技巧が使われたイディオムであり,結果的にそういう構文っぽく見えるようになっているに過ぎません。
イディオム解読
まず,\if
は,大雑把に言えば「続く2つの文字が等しいかどうかで条件分岐」という挙動をします。よって,#1
が空文字列の場合,\if"#1"
は \if""
となり,続く2つの文字 "
と "
が等しいので,条件は「真」と判定されて,「真」の側のトークン列 EMPTY
が実行され,\else
側は無視されます。
一方,#1
が例えば abcde
の場合,
\if"abcde"% EMPTY% \else [abcde]% \fi
という状態になっています。すると, \if"abcde"
の部分において,\if
に続く2文字は "
と a
です。この2つは等しくないため,条件は「偽」と判定されて,「真」の側のトークン列 bcde"EMPTY
の部分が無視されます。そして,\else
側のトークン列 [abcde]
が実行されるというわけです。
制約
この動きを見れば分かるように,上記イディオムは「#1
の先頭に "
という文字が来ない」ということを仮定しています。もし #1
に "abcde
という文字列を代入すると,次のようになってしまいます。
\if""abcde"% EMPTY% \else ["abcde]% \fi
この場合,\if""abcde"
の部分において,\if
に続く2文字は "
と "
なので等しく,条件判定は「真」となります。よって,「真」の側のトークン列 abcde"EMPTY
が実行され,期待と異なる結果を生むことになるでしょう。
よって,#1
の先頭に "
がありうるという場合は,このイディオムはこの形のままでは使えません。もし,「#1
の先頭に "
はありうるが ☃
はありえない」と仮定できる場合であれば,
\def\hoge#1{% \if ☃#1☃% EMPTY% \else [#1]% \fi }
と,#1
を ☃
でサンドイッチしておけばOKです(Unicode対応エンジンの場合)。