ト部蛸焼のブログ

日頃知ったことをアウトプットするためのブログ

カウンタとif文でうまいこと問題番号を付ける

この記事が目指す出力

f:id:tobetakoyaki:20191014220818p:plain:w500
問題1.や問題2.(1)などを出力します

目標

LaTeXでレポートを書くことが多い私ですが,問題番号をいちいち「問題1.」「問題2.」と太字にして入力するのは時に面倒です。この面倒を解決して,一つのコマンドで「問題1.」「問題2.」と順番に出力するためにはどうすればよいでしょうか。


素朴な方法

特別な方法を使わずに逐一太字として入力していくと次のような出力を得ます。今回はこれが出力の完成形です。次の点に注意します。

  • 大問番号「問題1.」や小問番号「(1)」は太字
  • これらはインデントなしで出力
  • 大問どうしは2行あけて出力
  • 小問どうしは1行あけて出力

f:id:tobetakoyaki:20191014181455p:plain:w500
完成イメージ

さて,この完成イメージを出力するコードに次のようなものがあります。問題番号に関連するところだけを抜粋します。

\noindent\textbf{問題1.}\par
% ここに解答が入る %
\vspace{2\baselineskip}
\noindent\textbf{問題2.}\par
\noindent\textbf{(1)}
% ここに解答が入る %
\vspace{\baselineskip}
\noindent\textbf{(2)}
% ここに解答が入る %
\vspace{2\baselineskip}
\noindent\textbf{問題3.}\par

「ひじょーにわるい」
たった4文字「問題1.」と出力するためにいろいろ書いている。5問解くレポートに対してこれを5回も打つなんて私は嫌です。面倒です。そういうことにしておきましょう。また,可読性も低い。やはり,何をするにも「後から見返しやすい」というのは大事なことです。

そこで,

  1. 繰り返される出力を簡単に出力できるようにする
  2. texファイルは可読性の高い状態にする

の両方を実現するためにコマンドを用いていきます。


コマンドできたよ (I) 〜カウンタで番号を管理する〜

コマンドを作る

コマンドを作るのは難しくありません。今回であれば 繰り返し使われているコードに予め名前をつければよいのです。
例えば,

\newcommand{\toi}[1]{\noindent\textbf{問題#1.}\par}

と予め定義をしておく*1と,次の2つが同じ役割を演じるようになります。そのため,「1つ目の書き方」を使えば楽に得たい出力が得られます。コマンド定義を使うと長々とした入力を省略することができて便利です。

%1つ目の書き方
\toi{1}

%2つ目の書き方 (最初に使っていたもの)
\noindent\textbf{問題1.}\par



カウンタの使用

さらにカウンタを使って入力の手間を減らします。数字が順番に並ぶことが分かっているので,それらを自動的に出力してくれるように変更します。

カウンタは文字通り数を数える変数です。今回使う範囲+αに限ってその使い方を列挙します*2

% これで"countername"という名前のカウンタが定義される。初期値は0。
\newcounter{countername}

% カウンタ「countername」の値を1だけ増やす。
\refstepcounter{countername}

% カウンタ「countername」の値を好きな値 (整数) に変更できる。
\setcounter{countername}{好きな値}

% カウンタ「countername」の値を使うことができる。ただし出力はされない。
\value{countername}

% カウンタ「countername」の値を出力できる。
\the\countername
\thecountername

なお,カウンタを定義するときに

\newcounter{countername}[othercounter]

とオプション引数*3で他のカウンタの名前を入力すると,そのカウンタの子カウンタとしてカウンタ「countername」が定義されます。つまり,カウンタ「othercounter」が更新されると,カウンタ「countername」は初期値にリセットされます*4

それではカウンタを用いて,先ほどのコマンド「\toi」を改変してみましょう。問題番号を勝手に出力してほしいので,LaTeX側がそれを管理しておく必要があります。今回はこれを「toinum」というカウンタを新たに定義することで管理します。

% 問題番号を管理するカウンタを定義
\newcounter{toinum}
% コマンド\toiの定義を変更
\newcommand{\toi}{
  \refstepcounter{toinum}
  \noindent\textbf{問題\thetoinum.}\par
}

こうすると,

\toi \toi \toi

と打てば「問題1. 問題2. 問題3. 」と出力されるようになります。

この部分までの変更を小問番号の方にも適用すると,「ひじょーにわるい」だったコードは次のようになります。

% プリアンブル
\newcounter{toinum}
\newcommand{\toi}{
  \refstepcounter{toinum}
  \noindent\textbf{問題\thetoinum.}\par
}

\newcounter{subtoinum}[toinum]
\newcommand{\subtoi}{
  \refstepcounter{subtoinum}
  \noindent\textbf{(\thesubtoinum) }
}

% 本文
\toi % 問題1.
% ここに解答が入る %
\vspace{2\baselineskip}
\toi % 問題2.
\subtoi % (1)
% ここに解答が入る %
\vspace{\baselineskip}
\subtoi % (2)
% ここに解答が入る %
\vspace{2\baselineskip}
\toi %問題3.

プリアンブルに書いた部分は別のコマンド定義ファイルに書いておけばファイル内で明記する必要はありませんので,結局,本文を執筆するときにはかなりの楽ができることになります。


コマンドできたよ (II) 〜if文を使ってみる〜

単純な置き換えの問題点

さて,ここまででコマンドの定義を二段階に分けて行ってきましたが,こう見ると,「\vspace{\baselineskip}」という部分が気になります。これも先ほどと同じようにコマンド定義に含めてしまいたいところです。そのままコマンド定義に含めると次のようになるかと思います。

% プリアンブル
\newcounter{toinum}
\newcommand{\toi}{
  \refstepcounter{toinum}
  \vspace{2\baselineskip}
  \noindent\textbf{問題\thetoinum.}\par
}

\newcounter{subtoinum}[toinum]
\newcommand{\subtoi}{
  \refstepcounter{subtoinum}
  \vspace{\baselineskip}
  \noindent\textbf{(\thesubtoinum) }
}

% 本文
\toi % 問題1.
% ここに解答が入る %
\toi % 問題2.
\subtoi % (1)
% ここに解答が入る %
\subtoi % (2)
% ここに解答が入る %
\toi %問題3.

このコードに対する出力は次のようになります。

f:id:tobetakoyaki:20191014191811p:plain:w500
簡単な置き換えによる出力

「これでよしっ!」と思いたいところですが,よく見ると,問題2. (1)の間にまで空行が入っています*5。これはまた見栄えが良くない。

「ひじょーにわるい」

これは例外的な処理を行う必要があるようです。つまり,

  • カウンタsubtoinumの値が1のときは空行を入れない。
  • カウンタsubtoinumの値が2以上のときは空行を入れる。

を満たすように処理をしてやる必要があるようです。これを実現するのはif文でしょう。LaTeXでif文を使います。「できるの?」と思われるかもしれませんが,できます。少しくらいなら。


if文の使用

LaTeXで使えるif文に関しては,詳しくは別のサイト*6に譲りますが,例えば以下のようなものがあります*7

% 単純な数の比較 (<, >, or =)
\ifnum [整数A] [比較演算子] [整数B] <してほしい処理>\fi

% 奇数判定
\ifodd [整数A] <してほしい処理>\fi

% 文字コードの一致判定
\if [トークンA] [トークンB] <してほしい処理>\fi

このとき気をつけたいのが,グルーピング*8にはうまく対応していないことです。例えば下の2つの\ifnumのうち,上の方はコンパイルエラーになってしまいます*9。注意が必要です。

\def\valueofa{3}
% 誤ったコード
\ifnum {\valueofa}=3 等しい \fi
% 正しいコード
\ifnum \valueofa=3 等しい \fi

さて,このif文を用いると,先ほどの簡単な置き換えをしただけのコードは次のように書き換えられます。

% プリアンブル
\newcounter{toinum}
\newcommand{\toi}{
  \refstepcounter{toinum}
  \ifnum \value{toinum}>1 \vspace{2\baselineskip}\fi
  \noindent\textbf{問題\thetoinum.}\par
}

\newcounter{subtoinum}[toinum]
\newcommand{\subtoi}{
  \refstepcounter{subtoinum}
  \ifnum \value{subtoinum}>1 \vspace{\baselineskip}\fi
  \noindent\textbf{(\thesubtoinum) }
}

% 本文
\toi % 問題1.
% ここに解答が入る %
\toi % 問題2.
\subtoi % (1)
% ここに解答が入る %
\subtoi % (2)
% ここに解答が入る %
\toi %問題3.

このコードによる出力は次のようになります。

f:id:tobetakoyaki:20191014220818p:plain:w500
if文を用いたコードの出力

これで正しい出力を得ることができました。その上,コードもすっきりしたものになりました。めでたしめでたし。


まとめ

いかがでしたか。カウンタとif文でうまいこと問題番号を付けることができました。

*1:コマンドはプリアンブルで定義するのが普通ですが,簡単な定義しか行わない場合には本文を始めてから定義することも可能です。ただし,最初にそのコマンドを使用する場所よりも前で定義する必要があります。

*2:ちなみに,通常のセクション番号やサブセクションの番号などもこのカウンタで管理されています。そのため,論文の執筆中にセクション番号を参照したくなったら,\thesectionと打てばよいということになります。これは例えば式番号を(1.2)のようなセクション番号付きにしたいときなどに使われます。

*3:オプション引数とは[ ]で囲まれて入力する引数のことです。このコマンドで言えば「countername」という部分の引数は必ず入力しなければなりませんが,[ ]で囲まれた「othercounter」の部分の引数は必ずしも入力する必要はありません。使うか選択できるという意味でオプショナルな引数です。

*4:子カウンタとして身近なものにはカウンタ「section」に対するカウンタ「subsection」があります。確かにセクションが改まるとサブセクションの番号は自動的に1に戻っています。より正確にはカウンタの値は0に戻り,refstepcounterで更新されて出力することで値としては1から出てくるのですが。

*5:先のコードを\newcommandで定義されたもので置き換えて読んでいくと「問題1.」の前にも2行の空行が入るはずなのですが,先の図を見るとこちらは目立ちません。これは2段組にしたためだと思われますが,1段組のときはそうはならないので,一般にはコマンド「\toi」のほうにも以下に述べるような処理が必要となります。

*6:例えば http://www.yamamo10.jp/yamamoto/comp/latex/make_doc/macro/index.php があります。

*7:なお,ifthenパッケージを使うともう少し違う記述の仕方も可能のようですが,今回は割愛します。

*8:{}を使って文字列などをひとまとまりとみなすこと。\frac{1}{3}などに使う{}もこれ。これがないと1文字で1つの引数と見なされる。つまり,\frac 13 は\frac 13と出力されるが,\frac 123 は\frac 12 3と出力される。

*9:\ifnum構文では2つ目の引数に比較演算子が来ることが要請されます。しかし,初めの書き方だと1つ目の引数が「{」で2つ目の引数が「\valueofa」と解釈されてしまいます。そのため,エラーが吐かれます。