以下の内容はhttps://zrbabbler.hatenablog.com/entry/2025/07/10/213406より取得しました。


イマドキのエ~アイのTeX知識を添削してみた(4)

前回までは実用的なTeX言語プログラミングを題材にしてきたわけですが、TeX言語においては芸人的なプログラミングもそれと同じくらい重要な能力です(多分 🙃)

というわけで、本記事ではTeX芸的な課題を扱います。

イマドキのAIのFizzBuzzを添削する

まずは、例のラング・ラグーの記事にも出てきたFizzBuzzです。例の記事と同じく「FizzBuzzとは何か」の説明はしませんが、組版的な要求やインタフェースに関する仕様は明示します。

※この問題ではGemini 2.5 Proを使用しました。

入力したやつ

以下の内容をMarkdown形式で表して入力しました。

以下の条件に従って、TeX言語でFizzBuzzをするマクロを実装してください。

  • \FizzBuzz{<整数n>}で1からnまでのFizzBuzzの結果を版面に出力する。
  • 各単語の間は空白で区切り、最初と最後に改段落を入れる。
  • LaTeX上で動くマクロを作る。パッケージを使ってはいけない。

出力されたやつ

最終的なプログラムコードだけを載せます。
\makeatletter
% --- 内部で使用するカウンタと条件フラグを定義 ---
\newcount\@fizzbuzz@i      % 1からnまでループするためのカウンタ
\newcount\@fizzbuzz@temp   % 割り算の中間計算用カウンタ
\newif\if@fizzbuzz@printed % "Fizz"か"Buzz"が出力されたかを判定するフラグ

% --- \FizzBuzz{n} マクロの定義 ---
% 1からnまでのFizzBuzz結果を出力します
\def\FizzBuzz#1{%
  \par % 1. 出力の前に改段落
  \@fizzbuzz@i=1 % カウンタを1で初期化
  \loop % 2. ループを開始
    % --- 各数値に対するFizzBuzz処理 ---
    \@fizzbuzz@printedfalse % フラグをリセット
    
    % 3の倍数か判定
    \@fizzbuzz@temp=\@fizzbuzz@i
    \divide\@fizzbuzz@temp by 3
    \multiply\@fizzbuzz@temp by 3
    \ifnum\@fizzbuzz@temp=\@fizzbuzz@i
      Fizz%
      \@fizzbuzz@printedtrue
    \fi
    
    % 5の倍数か判定
    \@fizzbuzz@temp=\@fizzbuzz@i
    \divide\@fizzbuzz@temp by 5
    \multiply\@fizzbuzz@temp by 5
    \ifnum\@fizzbuzz@temp=\@fizzbuzz@i
      Buzz%
      \@fizzbuzz@printedtrue
    \fi
    
    % "Fizz"でも"Buzz"でもなければ、数値そのものを出力
    \if@fizzbuzz@printed\else
      \the\@fizzbuzz@i
    \fi
    % --- 処理ここまで ---
    
  \ifnum\@fizzbuzz@i < #1 % 3. カウンタがn未満ならループを継続
    \space % 次の単語との間に空白を挿入
    \advance\@fizzbuzz@i by 1 % カウンタを1増やす
  \repeat % ループの先頭に戻る
  \par % 4. 全ての出力の後に改段落
}
\makeatother

添削してみるやつ

ロジックは完璧です😃

  • マクロ定義中に空の行があるが、これだと余計な改段落が起こるので削除した1
  • 40行目で引数の数値#1を空白文字で終結2させているが、#1が内部値で与えられる可能性を考慮して\relaxに置き換えた。
  • 例によって\makeatletterイラネ😐
添削結果

修正後のコード:

実行例
\documentclass{article}
\usepackage{ai-coding-3}
\begin{document}
\FizzBuzz{1}
\FizzBuzz{8}
\FizzBuzz{18}
\FizzBuzz{100}
\end{document}

出力結果
カンペキ😍

イマドキのAIのNabeAzzを添削する

例の記事ではFizzBuzzに続いてNazeAzzを出題していますが、結果的に「NabeAzzを知っているか」という知識問題という形で終わっています。ここでは知識ではなくプログラミングを調べたいので、元々の問題文(要件が書いてある)の文章を与えることにします。

※ただしLaTeXフォーマットを前提にした上で若干条件を整理しました。

※この問題ではGemini 2.5 Proを使用しました。

入力したやつ

以下の内容をMarkdown形式で表して入力しました。

以下の動作を行うTeX言語のマクロ\NabeAzzを作れ。

\NabeAzz{<整数n>}は1からnまでの整数の十進表記を順に空白区切りで出力する。ただし、整数が「3の倍数である」または「(その十進表記に)数字3を含む」場合は、フォントを「Computer Modern Funny Italic」でサイズを\largeに変えて出力する。その他の条件は以下の通り。

  • LaTeXフォーマットを前提とする。パッケージを使用してもかまわない。
  • 引数の<整数n>は正であり、かつ数字列で与えられると仮定してよい。想定外の引数を与えた場合の動作は何も規定しない。
  • 「Computer Modern Funny Italic」のTeXでのTFM名)はcmfi10である。LaTeX では「エンコーディングOT1、ファミリcmfr、シリーズm、シェープit」に割り当てられている。

出力されたやつ

最終的なプログラムコードだけを載せます。
%=============== フォント定義 ===============
% Computer Modern Funny Italic (cmfi10) を OT1/cmfr/m/it に割り当て
\DeclareFontFamily{OT1}{cmfr}{}
\DeclareFontShape{OT1}{cmfr}{m}{it}{<-> cmfi10}{}

%=============== マクロ定義 ===============
\ExplSyntaxOn

\NewDocumentCommand{\NabeAzz}{ m }
 {
  \int_step_inline:nn { #1 }
   {
    \int_compare:nF { ##1 == 1 } { ~ }
    \bool_if:nTF
     {
      ( \int_mod:nn { ##1 } { 3 } == 0 )
      ||
      ( \str_if_contains_p:nn { \int_to_arabic:n {##1} } { 3 } )
     }
     {
      { \large \fontfamily{cmfr}\fontshape{it}\selectfont \int_to_arabic:n {##1} }
     }
     {
      \int_to_arabic:n {##1}
     }
   }
 }

\ExplSyntaxOff

添削してみるやつ

Geminiの回答には次のような文章が含まれていました。

実装には expl3 プログラミング層(xparseパッケージを通じて読み込まれます)を利用します。これにより、数値計算や文字列の判定が容易になります。

この問題では今までと違って「パッケージを使用してもかまわない」としたわけですが、するとAIはexpl3を使ってきたようです😲

なんかスゴそうなので、早速実行してみると……

! Missing number, treated as zero.
<to be read again>
                   \__bool_=_1:
l.4 \NabeAzz{40}

?

……ざんねん🙃

アホになる条件

現状のコードでは“アホになる”(CM Funny Italicで出力される)整数の条件が次のようになっています。

    \bool_if:nTF
     {
      ( \int_mod:nn { ##1 } { 3 } == 0 )
      ||
      ( \str_if_contains_p:nn { \int_to_arabic:n {##1} } { 3 } )
     }

一見合ってそうですが、よく見ると前の条件(3の倍数)で条件関数が抜けています。ここは\int_compare:~の述語形式(_p)を補う必要があります。

      ( \int_compare_p:n { \int_mod:nn { ##1 } { 3 } == 0 } )

後の条件では\str_if_contains:~の述語形式が書いてあって正しそうですが、実はexpl3にこの名前の関数はありません😲 部分文字列判定の関数の名前は正しくは\str_if_in:~です。では\str_if_in_p:nnと書けばよいのかというと、それも不正解です。expl3の規則として、述語形式が使える条件関数は完全展開可能なものに限られるからです。(\str_if_in:nnは完全展開可能ではありません。)簡潔に書くのは難しそうなので、条件文形式を用いてbool変数\l_tmpa_boolに判定結果を一旦代入することにします。(もっといい方法がある?🤔)

    % "3を含むか"の判定結果を \l_tmpa_bool に代入
    \str_if_in:nnTF { \int_to_arabic:n {##1} } { 3 }
     { \bool_set_true:N \l_tmpa_bool }
     { \bool_set_false:N \l_tmpa_bool }
    % 整数 ##1 のアホ性の判定
    \bool_if:nTF
     {
      ( \int_compare_p:n { \int_mod:nn { ##1 } { 3 } == 0 } )
      ||
      \l_tmpa_bool
     }

とりあえずこれだけ修正すれば、動作としては正常になります。

展開制御な話

ところが、先ほどのコードの「3を含むか」の部分にはまだ問題があります。

    \str_if_in:nnTF { \int_to_arabic:n {##1} } { 3 }

ここで引数##1には何らかの「整数を表すトークン列」(例えば6とか\c_one_intとか)が与えられるはずですが、ここでは仮に123としましょう。

    \str_if_in:nnTF { \int_to_arabic:n {123} } { 3 }

ここで判定したいのは「\int_to_arabic:n {123}の値である1233を含むか」であるはずです。しかし\str_if_in:nn~(およびexpl3の標準関数の慣習)では「str型の引数がトークン列(n引数)で与えられる場合はそのトークン列を展開せずにトークン化した結果が使われる」ことになっています。つまりこの場合は「\int_to_arabic:n {123}という文字列」でこれは意図に反します。意図通りにするには\str_if_in:nn~の第1引数を完全展開する必要があります。\str_if_in:en~は用意されてないので\exp_args:Neを使いましょう。

    \exp_args:Ne \str_if_in:nnTF { \int_to_arabic:n {##1} } { 3 }

なお、引数は何らかの「整数を表すトークン列」と先ほど言いましたが、少なくとも実際の動作としては\int_step_~系の関数において実引数として渡される整数は「典型的な十進表記」の形をしています3。この前提の下では「\int_to_arabic:n {<十進表記>}3を含むか」は「<十進表記>3を含むか」と同値になるので、結局元のままでも動作としては意図通りになるようです。しかし、もし仮にこの性質を仕様として仮定するのであれば\int_to_arabic:n自体が不要であり、コード中の\int_to_arabic:n {##1}は全て##1に置き換えるべきでしょう。

アホなフォントへの切替

「CM Funny Italic」への切替のコードは現状では以下のようになっています。

\fontfamily{cmfr}\fontshape{it}\selectfont

しかし「CM Funny Italic」のシェープは飽くまで“OT1/cmfe/m/it”なので、このコードでは現在状態が「エンコーディングがOT1、シリーズがm4」でないと「CM Funny Italic」が選ばれません5。4属性を全て固定させたいので、代わりに\usefontを用いることにします。

\usefont{OT1}{cmfr}{m}{it}
例によって(ry

これまでの問題と同様に、実装したコードはLaTeXのパッケージとして使えるようにしたいところです。LaTeXのパッケージをexpl3で実装する場合はファイルの先頭に(\ProvidesPacakgeの代わりに)\ProvidesExplPackageを書くというお作法になっています。

\ProvidesExplPackage {ai-coding-4} {2025-07-07} {0.1}
  {NabeAzz by AI coding}

この宣言を書いておくと“expl3用のカテゴリコード”が自動でイイカンジに取り扱われます6。パッケージの中の\ExplSyntaxOnは避けたいので\ProvidesExplPackage宣言を利用しました。

添削結果

修正後のコード:

実行例
\documentclass{article}
\usepackage{ai-coding-4}
\begin{document}
\NabeAzz{40}
\end{document}

出力結果
一件落着😊

まとめ

というわけで、エ~アイに「TeXマクロを書いて」とお願いするとエ~アイがexpl3のコードを書いてくる可能性があるので、皆さん、expl3も学習しましょう!💁(マジで🙂)


  1. ちなみに、(La)TeXのマクロのコード中で「見やすさのために行を空けたい」という場合は「%だけ書いた行」を入れます。%だけの行は完全に無視され動作に影響を与えません。
  2. ちなみに、もしここで誤って#1%のように空白を削ってしまうと、TeXは数字列を終結させるために次の行の\spaceを食ってしまって、結果的に空白が出力されなくなります。
  3. interface3においてこの性質は明記されていません。ただし、\int_step_function:~\int_step_tokens:~の箇所にある例(整数を印字した結果が“1”や“2”になる)は当該の性質を示唆しています。
  4. 「CM Funny Italicという書体」という意味で捉えるならシリーズを固定する必要はないかもしれませんが、何れにしても「CM Funny Italic」にはシリーズmしかありません。
  5. 例えばLuaLaTeXではエンコーディングの既定値がTUであり、この状態では「CM Funny Italic」への切替は失敗します。
  6. 当該のパッケージファイルを読み込んでいるときにだけ\ExplSyntaxOnの状態になり、そこから別のファイルを読み込む場合には自動的に\ExplSyntaxOffに戻ります。



以上の内容はhttps://zrbabbler.hatenablog.com/entry/2025/07/10/213406より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14