Help us understand the problem. What is going on with this article?

LaTeX で条件分岐や反復処理:ifthen パッケージのキホン

More than 1 year has passed since last update.

まえがき

もちろん、LaTeXは(TeX言語と違って)プログラム言語ではないので、条件分岐や繰り返し処理のような“実行制御”なんてアリエナイ、というタテマエがあることは皆さんご存知でしょう。とはいっても実際には「LaTeXにもif文があったらいいのにな……」と思った事があるLaTeXユーザはいるのではないでしょうか。

実は“標準LaTeX”にif文はあります。 LaTeXの開発元(LaTeX3 Project Team)による配布物の中には、「標準パッケージ(standard packages)」1と呼ばれる一連のパッケージ群があり、その中のifthenパッケージがまさに「LaTeXに実行制御の機能を追加する」ためのものなのです。つまり「LaTeXに実行制御はない」というタテマエなんて実は無かったのでした2

本記事では、ifthenパッケージとその周辺についてのキホンについて解説します。

注意

ifthenパッケージは“普通のLaTeXユーザ”向けのものであるので、解説は全てLaTeXの文法の範囲で行います。しかし必要に応じて、TeX言語者向けの補足説明を行うことがあります。その場合は注意のために :sushi: を付けることにします。例えば以下の通りです。

:sushi: TeX言語はアレ!

パッケージ読込

プレアンブルに次のように書きます。パッケージオプションはありません。

\usepackage{ifthen}

※この記事で紹介する例は全て、プレアンブルでifthenパッケージを読みこんでいる前提とします。

条件分岐 ― \ifthenelse

書式
\ifthenelse{«条件»}{«真»}{«偽»}

«条件»が成立すれば«真»を、しなければ«偽»を実行します。

:sushi: 完全展開可能ではありません。

%%%%(プレアンブル)
\newcounter{verbosity}
\setcounter{verbosity}{2}
%%%%(文書本体)
\ifthenelse{\value{verbosity} > 1}{%
  % カウンタ verbosity の値が2以上ならこちらを実行(出力)
  {\TeX}はアレでアレでアレでアレでアレ、さらにアレ!%
}{%else
  % それ以外はこちらを実行(出力)
  {\TeX}はアレ!%
}

繰り返し実行 ― \whiledo

書式
\whiledo{«条件»}{«何か»}

«条件»が成立する間、«何か»を繰り返し実行します。

:sushi: もちろん完全展開可能ではありません。

%%%%(プレアンブル)
\newcounter{count}
%%%%(文書本体)
{\TeX}%
% "とっても"を5回繰り返す
\setcounter{count}{5}%
\whiledo{\value{count}>0}{%
  とっても\addtocounter{count}{-1}}%
アレ!

条件部の書き方

\ifthenelse\whiledoにおける«条件»の部分は、以下の要素を組み合わせて記述します。

  • 命題: 真偽値に評価される式のことです。詳細は後述。

    12 > 3           %==>真
    \equal{FOO}{foo} %==>偽
    \boolean{true}   %==>真
    
  • \NOT: 単項演算子です。

    \NOT 42 > 42     %==>真
    
  • \AND\OR: 二項演算子です。左結合で、\AND\ORの間に優先順位はありません。

    1<2 \OR 3<4 \AND 5>6 %==> "(真 or 真) and 偽"なので偽
    
  • \(...\): 演算子の優先順位を変えます。

    1<2 \OR \(3<4 \AND 5>6\) %==> "真 or (真 and 偽)"なので真
    \NOT \(1<3 \AND 3<5\)    %==> "not (真 and 真)"なので偽
    

\NOT \AND \ORは(恐らく過去版との互換性のため)小文字で\not \and \orと書くことも許されていますが、これらの命令名は既存のものと衝突しているので使うべきでありません3

命題の書き方

  • «整数» < «整数»«整数» = «整数»«整数» > «整数»: 整数どうしを比較します。

    %% カウンタ'tocdepth'の値が3より大ならば
    \value{tocdepth} > 3
    %% ≦,≧は直接書けないので"3以下"は次のように書く
    \NOT \value{tocdepth} > 3
    
  • \lengthtest{«長さ» < «長さ»}\lengthtest{«長さ» = «長さ»}\lengthtest{«長さ» > «長さ»}: 長さどうしを比較します。

    %% 長さ変数 \parindent が正ならば
    \lengthtest{\parindent > 0pt}
    
  • \equal{«文字列»}{«文字列»}: 2つの文字列が等しいかを判定します。
    文字列の中に(保護付でない)(La)TeXマクロがある場合は展開されます。

    %% 現在の節番号が'B'ならば
    \equal{\thesection}{B}
    
  • \isundefined{\命令}: その命令(制御綴)が未定義であるかを判定します(定義されていれば偽)。

    %% \chapter 命令が存在しないならば
    \isundefined{\chapter}
    
  • \isodd{«整数»}: 整数が奇数であるかを判定します。

    %% カウンタ'figure'の値が奇数ならば(なんだそりゃ)
    \isodd{\value{figure}}
    

    ※恐らく「奇数か偶数か」の判定が一番有効なのはページ番号についてでしょう4。しかし残念ながら標準LaTeXにおいては「現在のページ番号」を得る機能は存在しません。(\value{page} は「現在のページ番号」とは必ずしも一致しない。詳細については「LaTeXで現在のページ番号を取得・出力する話」を参照されたい。)

  • \boolean{«変数名»}: 真偽値変数を参照します。詳細は後述。

値の書き方

整数や文字列などの“値”をどう書くか、また“変数”をどうやって使うか、については基本的に「LaTeXの文法に従う」わけですが、あまり馴染みのない人も多いと思われますので、必要最低限の説明をしておきます。

整数値

命題の記述の中の«整数»の箇所には以下のものを書くことができます。

  • 42-3等の、数字列で表される即値。
  • \value{«カウンタ名»}: そのカウンタの現在の値。
  • 上掲のものに展開されるLaTeXマクロ。
  • :sushi: その他、TeX言語で整数と解釈されるトークン列全て。

カウンタを扱うための命令には以下のものがあります。

  • \newcounter{«カウンタ名»}: 新たにカウンタを定義します。
  • \setcounter{«カウンタ名»}{«整数»}: カウンタの値を変更します。
  • \addtocounter{«カウンタ名»}{«整数»}: 現在のカウンタの値に引数の値を加算します。
  • calcパッケージを読み込むと、\setcounter\addtocounterの引数の«整数»6*9等の式を書くことが可能になります5

※カウンタ値の操作のスコープはグローバルです。つまり{...}によるグループ化の影響をうけません。

%%%%(プレアンブル)
\usepackage{ifthen,calc}
\newcommand*{\SIX}{6}
\newcounter{Nine}\setcounter{Nine}{9}
\newcounter{Test}
%%%%(文書本体)
\setcounter{Test}{\SIX*\value{Nine}}%54が代入される
\newcommand*{\Answer}{42}
\ifthenelse{\value{Test}=\Answer}% 54=42 となり偽
  {The universe goes well.}
  {The universe goes wrong!!}%こっちが出力される

長さ値

命題の記述の中の«長さ»の箇所には以下のものを書くことができます。

  • 42pt-1in1.5em等の「単位付実数」で表される即値6
  • \parindentなどの長さ変数である命令: その長さ変数の値。
    ※変数の値が伸縮付長さ(rubber length)である場合はその自然長を用いて比較が行われます。
  • 上掲のものに展開されるLaTeXマクロ。
  • :sushi: その他、TeX言語で寸法値(dimen)と解釈されるトークン列全て。

長さ変数を扱うための命令には以下のものがあります。

  • \newlength{\命令}: 新たに長さ変数を定義します。
  • \setlength{\命令}{«長さ»}: 変数の値を変更します。
  • \addtolength{\命令}{«長さ»}: 現在の変数の値に引数の値を加算します。
  • calcパッケージを読み込むと、\setlength\addtolengthの引数の«長さ»210mm-1in等の式を書くことが可能になります。

※長さ変数の操作のスコープはローカルです。つまり{...}によるグループ化の影響をうけます。

%%%%(プレアンブル)
\usepackage{ifthen,calc}
\newlength{\lengthA}\setlength{\lengthA}{10pt + 5pt}
\newlength{\lengthB}\setlength{\lengthB}{10pt plus 5pt}
%%%%(文書本体)
\newcommand*{\OneInch}{1in}
% "1in(=72.27pt) > 12pt"となり真
\ifthenelse{\lengthtest{\OneInch > 12pt}}{Yes!}{No!}
% "15pt > 12pt"となり真
\ifthenelse{\lengthtest{\lengthA > 12pt}}{Yes!}{No!}
% "10pt(\lengthB の自然長) > 12pt"となり偽
\ifthenelse{\lengthtest{\lengthB > 12pt}}{Yes!}{No!}
% この書き方は不可
%\ifthenelse{\lengthtest{10pt plus 5pt > 12pt}}{Yes!}{No!}

文字列

\equalの引数の文字列については、少なくとも以下のものを含めることができます。

  • 「LaTeXの特殊文字」でない文字
  • 均衡な{ }の組
  • \protectで保護された命令
  • 上掲のものの列に展開されるLaTeXマクロ

:sushi: 保護付で完全展開したトークン列について完全一致の判定している(という動作と等価な)ようです。

LaTeXマクロは展開されるため、\newcommandなどで定義した無引数のLaTeXマクロを文字列型の変数のように扱うことができます。詳しくは以下の記事を参照してください。

%%%%(プレアンブル)
\newcommand*{\lang}{kansai}% "文字列変数"的マクロ
%%%%(文書本体)
\ifthenelse{\equal{\lang}{kansai}}{%
  {\TeX}はアレやで!%
}{%else
  {\TeX}はアレですよ!%
}

真偽値

ifthenパッケージの機能として、真偽値型の“変数”を定義してそれを\ifthenelse\whiledoの命題として使うことができます。

  • \newboolean{«変数名»}: 新たに真偽値型の変数を定義します。
  • \setboolean{«変数名»}{«値»}: 変数の値を変更します。«値»truefalseです。
  • \ifthenelse\whiledoの条件部で\boolean{«変数名»}と書くとその変数の値をもつ命題として扱われます。

:sushi: 実は\newboolean\newifに対するラッパーです。つまり、「XXXという真偽値変数」の実体は「\ifXXXというスイッチ」だということです。

%%%%(プレアンブル)
\newboolean{Precise}%正確か?
\newboolean{Useful}%有用か?
\setboolean{Precise}{false}
\setboolean{Useful}{false}
%%%%(文書本体)
\ifthenelse{\boolean{Precise} \AND \boolean{Useful}}{%
  %何もしない
}{%else
  出力すべきものは何もありませんでした。
  \end{document}% めでたしめでたし
}

\maketitle
\begin{abstract}
近年、…… %以下いろいろと書いてある

ユーザが定義する変数の他に、LaTeXカーネルや各種パッケージにおいて定義される真偽値変数もあります。

※これらの変数に対しては\setbooleanで値を変更すべきでありません。

変数名 意味 パッケージ
true 常に真(定数)
false 常に偽(定数)
mmode 数式モードであるか
tdir 縦書きモードであるか 7
draft 下書きモード(draftオプション付)であるか 8
pdf PDFモードであるか9 ifpdf
xetex エンジンがXeTeXであるか ifxetex
luatex エンジンがLuaTeXであるか ifluatex
upTeX エンジンがupTeXであるか ifuptex
pTeX エンジンがpTeXであるか ifptex10

:sushi: この他でも、もしあるパッケージが \ifXXX というスイッチ(if-トークン)を定義しているならそれは XXX という真偽値変数として使えます。要するに次の2つが等価になるわけです。

\ifXXX «真» \else «偽» \fi % TeXのif文
\ifthenelse{\boolean{XXX}}{«真»}{«偽»}% LaTeXのif文

実用例

せっかく便利なパッケージの使い方を学んだのですから、色々やってみましょう!

その1: エンジンの種類により分岐

何かやんごとなき事情があって「複数のエンジンでコンパイルできる日本語文書」という変態なものを作りたい場合に、エンジンの種類により条件分岐を行い、それぞれ適切な処理を行う、という例です。

% pLaTeX/upLaTeX/LuaLaTeX/XeLaTeX文書
\documentclass[autodetect-engine,dvipdfmx-if-dvi,
  a4paper,ja=standard,jafont=kozuka-pr6n]{bxjsarticle}
\usepackage{ifthen}
\usepackage{ifxetex,ifluatex,ifuptex}
% \CID 命令を使いたいので...
\ifthenelse{\boolean{xetex}}{
  % XeLaTeXではzxotfを読み込む
  \usepackage{zxotf}
}{\ifthenelse{\boolean{luatex}}{
  % LuaLaTeXではluatexja-otfを読み込む
  \usepackage{luatexja-otf}
}{\ifthenelse{\boolean{upTeX}}{
  % upLaTeXではuplatexオプション付でotf
  \usepackage[uplatex]{otf}
}{%else (pLaTeXと仮定する)
  % pLaTeXではオプション無しでotf
  \usepackage{otf}
}}}
\begin{document}
% '葛'の特定の異体字を出力したい
\Large \CID{1481}城市から\CID{7652}飾区まで。
\end{document}

example1.png

その2: テキストの幅に応じて分岐

マクロの引数に与えたテキストが長いか短いかで処理を変える、という例です。

% upLaTeX文書
\documentclass[uplatex,dvipdfmx,a4paper]{jsarticle}
\usepackage{ifthen}
\usepackage{okumacro}% \kintou を使いたい
\usepackage{graphicx}% \resizebox を使いたい
\newlength{\OrigWidth}
%% \InWidth{«横幅»}{«テキスト»}
% テキストを指定の横幅に"イイカンジに"出力する.
\newcommand*\InWidth[2]{%
  \settowidth{\OrigWidth}{#2}% \settowidth 便利
  \ifthenelse{\lengthtest{\OrigWidth > #1}}{%
    % 指定幅より大きい場合は横方向に縮小する
    \resizebox{#1}{\height}{#2}% 横幅だけ変える
  }{%else
    % 指定幅以下の場合は均等割りを行う
    \kintou{#1}{#2}%
  }%
}
\begin{document}
\begin{center}\gtfamily
  \begin{tabular}{|c|}\hline
    \InWidth{8zw}{レット}                \\\hline
    \InWidth{8zw}{ミーニング}            \\\hline
    \InWidth{8zw}{ノーエクスパンド}      \\\hline
    \InWidth{8zw}{エクスパンドアフター}  \\\hline
    \InWidth{8zw}{アフターアサインメント}\\\hline
  \end{tabular}
\end{center}
\end{document}

example2.png

その3: LaTeXでFizzBuzz

とっても実用的な例。

% LaTeX文書, エンジン不問
\documentclass[a4paper]{article}
\usepackage{ifthen,calc}
\newcounter{Count}
\newcounter{Rem}
%% \FizzBuzz{«整数n»}
% 1からnまでの範囲でFizzBuzzする.
\newcommand*\FizzBuzz[1]{%
  \setcounter{Count}{0}%
  \whiledo{\value{Count} < #1}{%
    \addtocounter{Count}{1}%
    \IfMultiple{15}{FizzBuzz}{%
      \IfMultiple{3}{Fizz}{%
        \IfMultiple{5}{Buzz}{%
          \arabic{Count}% 算用数字で値を出力
    }}}\ % 空白を出力
  }%
}
%% \IfMultiple{«整数n»}{«真»}{«偽»}
% Countの値がnの倍数であるかを判定する.
\newcommand*{\IfMultiple}[3]{%
  % 余りを計算する
  \setcounter{Rem}{\value{Count}/#1*#1-\value{Count}}%
  \ifthenelse{\value{Rem}=0}{#2}{#3}%
}
\begin{document}
% 1から100までFizzBuzzする
\noindent \FizzBuzz{100}
\end{document}

example3.png

まとめ

LaTeXにif文はあります! ちょっとした実行制御ならばTeX言語は必要ありません! ifthenパッケージを活用しましょう!


  1. ifthen の他には fontenc、inputenc、latexsym 等が「標準パッケージ」に含まれます。 

  2. ちなみに、ifthen も含めて「標準パッケージ」は全て“普通のLaTeXユーザ”(文書作成者)が用いることを想定したものです。このことは usrguide で標準パッケージが紹介されていることから解ります。 

  3. :sushi: 特に \or は危険で、\ifcase...\fi の中に \or が紛れ込んでしまうと誤動作の原因になってしまいます。 

  4. そもそも \isodd なんて変な機能があるのはTeX言語に \ifodd というプリミティブが存在するからであり、TeXに \ifodd がある理由はページ番号の偶奇判定が必要だからです。 

  5. \ifthenelseの命題の記述の中で式が使えるようにはならないことに注意。 

  6. この場合、plus ... minus ...のような伸縮を付けることはできません。 

  7. (u)pLaTeXエンジンでのみ利用可能です。 

  8. jsclassesバンドルの文書クラス(jsarticle/jsbook/jsslide)でのみ利用できます。他の文書クラスで下書きモードであるかを判定したいときは、ifdraftパッケージを読み込んで、\ifdraft{«真»}{«偽»}とします。 追記:2016/07/13 以降のjsclassesではdraftスイッチが廃止されたので、他のクラスと同様ifdraftパッケージを利用する必要があります。 

  9. 「PDFモードである」というのは、エンジンがpdfTeXまたはLuaTeXであってかつPDFを書き出す状態である、ということ。 

  10. ifptexパッケージはTeX Live2017以降に収録されています。 

zr_tex8r
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away