3
5

More than 1 year has passed since last update.

LaTeXでプログラミングしてみよう(後編)

Last updated at Posted at 2022-03-31

この記事はLaTeXでプログラミングしてみよう(前編)の続きです。まだ読んでいない方は、先に前編を読むことをオススメします。


実践

さて、前編ではLaTeXでプログラミングするのに必要となる基礎知識を紹介しました。ここからは、それらを使って本格的なプログラミングをやってみましょう。

その前に

LaTeXでプログラミングをするのに必要な作法や応用テクニックを紹介します。もうちょっとだけ続きます。

名前空間

多くのオブジェクト指向言語では名前空間をサポートしていて、チームで開発するときに有効です。LaTeXでも\def等で既存のマクロを書き換えてしまわないように、名前の衝突を避けたいところです。ですが、LaTeXはオブジェクト指向言語ではないので、名前空間をサポートしていません。

そこで、LaTeXでは識別子に@を入れて名前空間を定めるという慣習があります。例として、二つの引数をとって足し算をする簡単なマクロを考えてみます。

足し算する
\makeatletter

\newcount\xxx@hoge@foo

\def\xxx@add#1#2{%
  \xxx@hoge@foo=#1
  \advance\xxx@hoge@foo #2
  \the\xxx@hoge@foo%
}

\makeatother

上記の例で言うと、xxxhogeが名前空間に該当します。LaTeXには名前空間という概念はありませんが、識別子にユニークな文字列を入れることで名前の衝突を避けるということです。基本的には3〜4文字で定めます。本稿では「alia」で統一します。

次に\makeatletter\makeatotherについて説明します。準備編でもさらっと使いましたが、何だこれ?と思った方も中にはいると思います。LaTeXでは、識別子には英文字しか使えないという決まりがあります。そのため、@を識別子の中で使うためには「@を英文字の一員として見なす」という命令が必要になります。その命令が\makeatletterです。逆に、「@を記号の一員として見なす」命令が\makeatotherというわけです。1

行末の改行文字

LaTeXでマクロを組んでいると、奇妙なスペースが入って困った経験はないだろうか?例えば次のようなケースを考えてみよう。

\documentclass[a4paper,uplatex]{jsarticle}
\newcommand{\foo}{
  FOO
}
\begin{document}
  HOGE\foo BAR
\end{document}

一見、何も問題ないように思えます。では、出力はどうでしょうか。

出力
HOGE FOO BAR

どうやら変なスペースが入っているようです。出力はHOGEFOOBARとなってほしいのに...:cry:

茶番はさておき、LaTeXにおけるマクロ定義では改行文字や行末の空白文字を読み飛ばしません。そのため、いつものコーディングのようにマクロ定義の中で改行を連発すると意図しない出力になります。そこで、行末にコメント文字%)を置くことで改行文字を読み飛ばすようにします。

\documentclass[a4paper,uplatex]{jsarticle}
\newcommand{\foo}{%
  FOO%
}
\begin{document}
  HOGE\foo BAR
\end{document}
出力
HOGEFOOBAR

基本的には、マクロの呼び出しやfor/whileループのカッコの後ろに置きます。逆に、数値の代入文や四則演算の式の後ろには置かないようにしましょう。

配列への代入

前編では配列の値の定義と使用を紹介しました。ですが、プログラミングをしていると配列の値を更新したくなる場面がたびたび登場します。そこで、後編では配列に値を代入する方法を紹介します。

配列の値を更新するには\@namedefを少し改造すれば出来そうです。そこで、\@namedefの定義を見てみましょう。

\def\@namedef#1{%
  \expandafter\def\csname#1\endcsname%
}

\expandafter2によって\csname#1\endcsname3を先に展開し、その後\defによって与えられた文字列から成るマクロを定義しています。しかし、マクロが引数で与えられるとどうでしょうか?\fooというマクロが与えられたと仮定すると、\@namedefの定義によって次のように展開されます。

\def\\foo

エスケープ文字が隣り合っており、明らかに文法が間違っています。\csnameはあくまで文字列から制御綴を作る命令であり、その文字列にはエスケープ文字が含まれてはいけません。なので、文字列にマクロを含まないように「完全展開」すれば良いと推測できます。つまり、\def\edefに変更すれば解決です。

\def\xxx@nameedef#1{%
  \expandafter\edef\csname#1\endcsname%
}

また、これを使って大量の配列を一気に定義できます。LaTeXの内部命令である\@forを使います。

\newcount\K
\K=0
\@for\X:={1,2,3,4,5}\do{%
  \xxx@nameedef{arr/\the\K}{\X}%
  \advance\K 1
}

文法は\@for\<VarName>:=<List>\do{<Stuff>}です。カンマで区切られたトークンのリストに対して処理を行います。Pythonのfor文のようなものです。

やってみよう

さて、全て準備は整いました。早速プログラムを組んでみましょう。いくつかの自作サンプルを紹介します。ここまで読んでくれた君ならきっと読めるはず!

素数の数え上げ

\documentclass[a4paper,uplatex]{jsarticle}

\makeatletter

\def\alia@primelist{2}

\newcount\alia@true
\newcount\alia@false
\newcount\alia@n
\newcount\alia@i
\newcount\alia@j
\newcount\alia@k
\alia@true=1
\alia@false=0
\alia@n=0
\alia@i=0
\alia@j=0
\alia@k=0

\def\alia@nameedef#1{%
  \expandafter\edef\csname#1\endcsname%
}

\newcommand{\primesuntil}[1]{%
  %% 配列を作成
  \alia@i=0
  \alia@n=#1
  \advance\alia@n 1
  \@whilenum{\alia@i<\alia@n}\do{%
    \alia@nameedef{alia@isprime/\the\alia@i}{\the\alia@true}%
    \advance\alia@i 1
  }%
%
  %% エラストテネスの篩
  \alia@i=2
  \@whilenum{\alia@i<\alia@n}\do{%
    \ifnum\@nameuse{alia@isprime/\the\alia@i}=\alia@true%
      \alia@j=2
      \multiply\alia@j\alia@i
      \@whilenum{\alia@j<\alia@n}\do{%
        \alia@nameedef{alia@isprime/\the\alia@j}{\the\alia@false}%
        \advance\alia@j\alia@i
      }%
      \ifnum\alia@i>2%
        \edef\alia@primelist{%
          \alia@primelist, \the\alia@i%
        }%
      \fi%
    \fi%
    \advance\alia@i 1
  }%
%
  %% 結果を出力
  \alia@primelist%
}

\makeatother

\begin{document}
  素数 $=$ \primesuntil{30}...
\end{document}

出力:
素数 = 2, 3, 5, 7, 11, 13, 17, 19, 23, 29...

Tips:

  • LaTeXにはTrue Falseのような真偽値は実装されていないので自分で用意する必要があります。今回はTrueを1、Falseを0として定義しています。
  • \alia@primelistを再帰的に再定義することで疑似的にリストとして表現しています。

バブルソート

\documentclass[a4paper,uplatex]{jsarticle}

\makeatletter

\newcount\alia@narr
\newcount\alia@x
\newcount\alia@y
\newcount\alia@i
\newcount\alia@j
\newcount\alia@k
\alia@narr=10
\alia@x=0
\alia@y=0
\alia@i=0
\alia@j=0
\alia@k=0

\def\alia@elements{0,1,7,2,5,3,8,6,9,4}

\def\alia@nameedef#1{%
  \expandafter\edef\csname#1\endcsname%
}
\def\alia@assign#1#2{%
  \alia@nameedef{#1}{\@nameuse{#2}}%
}
\@for\alia@e:=\alia@elements\do{%
  \alia@nameedef{alia@arr/\the\alia@k}{\alia@e}%
  \advance\alia@k 1
}

\newcommand{\printarr}{%
  \alia@k=0
  \@tempcnta=\alia@narr
  \advance\@tempcnta -1
%
  \@whilenum\alia@k<\@tempcnta\do{%
    \@nameuse{alia@arr/\the\alia@k}, %
    \advance\alia@k 1
  }%
  \@nameuse{alia@arr/\the\@tempcnta}%
}
\newcommand{\swaparr}[2]{%
  \alia@assign{alia@tmp}{alia@arr/#1}%
  \alia@assign{alia@arr/#1}{alia@arr/#2}%
  \alia@assign{alia@arr/#2}{alia@tmp}%
}
\newcommand{\bubblesort}{%
  \alia@i=0
  \alia@x=\alia@narr
  \advance\alia@x -1
%
  %% バブルソート本体
  \@whilenum{\alia@i<\alia@x}\do{%
    \alia@j=0
    \alia@y=\alia@narr
    \advance\alia@y -\alia@i
    \advance\alia@y -1
    \@whilenum\alia@j<\alia@y\do{%
      \@tempcnta=\alia@j
      \advance\@tempcnta 1
      \ifnum\@nameuse{alia@arr/\the\alia@j}>\@nameuse{alia@arr/\the\@tempcnta}%
        \swaparr{\the\alia@j}{\the\@tempcnta}%
      \fi%
      \advance\alia@j 1
    }%
    \advance\alia@i 1
  }%
}

\makeatother

\begin{document}
  \printarr

  $\downarrow$ バブルソート
  \bubblesort

  \printarr
\end{document}

出力:
0, 1, 7, 2, 5, 3, 8, 6, 9, 4
↓ バブルソート
0, 1, 2, 3, 4, 5, 6, 7, 8, 9

Tips:

  • \alia@assign配列の値をカウンタに代入するマクロです。\alia@nameedefだと代入先のカウンタは名前参照されますが、代入する値は名前参照されません。
  • \@tempcntaはTeX言語に元々実装されているカウンタで、ユーザが自由に使えるようになっています。@tempcntbもあります。

二分探索

\documentclass[a4paper,uplatex]{jsarticle}

\makeatletter

\def\alia@primelist{%
  2,3,5,7,11,13,17,19,23,29,%
  31,37,41,43,47,53,59,61,67%
}

\newcount\alia@nprimes
\newcount\alia@left
\newcount\alia@right
\newcount\alia@mid
\newcount\alia@diff
\newcount\alia@k
\newif\ifalia@found\alia@foundfalse
\alia@nprimes=19

\def\alia@nameedef#1{%
  \expandafter\edef\csname#1\endcsname%
}

%% 70までの素数の配列を作成
\alia@k=0
\@for\alia@p:=\alia@primelist\do{%
  \alia@nameedef{alia@primes/\the\alia@i}{\alia@p}%
  \advance\alia@k 1
}

\newcommand{\searchprime}[1]{%
  \alia@left=0
  \alia@right=\alia@nprimes
  \advance\alia@right -1
  \alia@diff=\alia@right
  \advance\alia@diff -\alia@left
%
  %% 二分探索
  \@whilenum{\alia@diff>1}\do{%
    \alia@mid=\alia@diff
    \divide\alia@mid 2
    \advance\alia@mid \alia@left
%
    \@tempcnta=\@nameuse{alia@primes/\the\alia@mid}
    \ifnum#1=\@tempcnta%
      \alia@foundtrue%
    \else\ifnum#1>\@tempcnta%
      \alia@left=\alia@mid
    \else%
      \alia@right=\alia@mid
    \fi\fi%
%
    %% 見つかったらループを抜ける
    \ifalia@found%
      \alia@diff=-1
    \else%
      \alia@diff=\alia@right
      \advance\alia@diff -\alia@left
    \fi%
  }%
%
  %% 結果を出力
  \ifalia@found%
    あります%
  \else%
    ありません%
  \fi%
}

\makeatother

\begin{document}
  素数の中に57は\searchprime{57}

  素数の中に37は\searchprime{37}
\end{document}

出力:
素数の中に 57 はありません
素数の中に 37 はあります

Tips:

  • \ifalia@foundは検索対象の数値が素数配列にある値にヒットしたかどうかのスイッチです。ヒットしたら\alia@foundtrueを呼び出します。

まとめ

LaTeXはレポートとか論文を書くためだけに使われがちですが、意外とプログラミング言語っぽいポテンシャルを秘めています。マクロ作成に慣れると、普段のレポートや論文作成のスピードが向上するかもしれません。この記事を読んで、少しでもLaTeXプログラミングに興味を持って頂けたなら幸いです。

参考文献

  1. このような命令が用意されている理由は、一般のユーザーがLaTeXの内部命令を不用意に使えないようにするためです。LaTeXでは、ユーザーがほぼ全ての命令をマクロとして自由に定義し直すことができてしまいます。すなわち、内部で使われている重要な命令の定義をユーザーが意図せず変更してしまう恐れがあります。その対策として、内部命令の名称に@を入れることで、通常の使い方ではアクセスできないように保護しています。

  2. B ⇒ Cに展開(一回展開)されるとき、\expandafterAB ⇒ AC。Bを先に展開してからAが実行されます。完全展開ではないことに注意。

  3. \csname HOGE\endcsname\HOGEのように、与えられた文字トークン列を名前とする制御綴に展開されます。与える文字トークン列を動的に変更できるのがポイントです。

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5