TeX Alchemist Online

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

\if 系トークンでの条件判定でよく使われるテク3選

今回は,\if系トークンでの判定でよく使われるテクニック3選として,次の3つの技巧を紹介しましょう。

  1. \if系トークンでの判定で OR 条件
  2. \if系トークンでの判定で AND 条件
  3. 空文字列判定

【目次】

LaTeXの ifthen パッケージとの比較

このような条件判定を行うには,LaTeXの ifthen パッケージが便利です。ifthen パッケージについては,次の記事にまとめられています。

qiita.com

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 かどうかで十の位が 10\value{b}<99 かどうかで一の位が 10 になり,全パターンで 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対応エンジンの場合)。