\NabeAzz を作ってみた

ZR さんのブログであるマクロツータに、 日本で TeX できる人の数→(?) - マクロツイーター のような投稿がありました。その投稿の NabeAzz にひとりで大爆笑してしまいました。

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

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

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

付帯条件:

  • フォーマットは plain TeX または LaTeX2e とする。
  • 使用フォーマットのユーザ命令、内部定義マクロ、TeX プリミティブの全てを自由に使える。
  • さらに外部パッケージを使用しても構わない。
  • シェル実行機能(\write18)は使用禁止。(←書き忘れてた ;-) )
  • 拡張エンジンの機能も使用可能である。ただし、「LuaTeX で Lua インタプリタを呼び出す」ことだけはダメ。;-)
  • 制限時間は実働で 24 時間とする。
  • あらゆる形態の参考文献を参照して構わない。ただし、「この問題の答えそのもの」および「TeX での FizzBuzz の実装そのもの」は除外する。


さて、あなたは TeX を使えますか……?

http://d.hatena.ne.jp/zrbabbler/20110815/1313398638

NabeAzz を TeX でやったらどうなるか面白そうなので、さっそくチャレンジしてみました。

NabeAzz.tex は、以下のとおりです。

%#!pdflatex NabeAzz
%#LPR acroread NabeAzz.pdf
\documentclass{article}

\makeatletter
\def\NabeAzz@font{\normalfont}%normal
\def\NabeAzz@h@font{%aho
  \usefont{OT1}{cmfr}{m}{it}\Large}%\large
\def\NabeAzz#1{%
  \@tempcnta\z@\relax%%loop counter
  \@whilenum \@tempcnta<#1 \do{%
    \advance\@tempcnta\@ne
    \@tempcntb\z@\relax
    %%case 1
    \chk@h@thr@@{\@tempcnta}{\@tempnuma}%
    \ifnum\@tempnuma=\@ne
      \@tempcntb\@ne\relax
    \fi
    %%case 2-1
    \@m@d{\@tempcnta}{3}{\@tempnuma}%
    \ifnum\@tempnuma=\z@
      \@tempcntb\@ne\relax
    \fi
    %%case 2-2
    %% \sum@@ight@digits{\@tempcnta}{\@tempnumb}%
    %% \@m@d{\@tempnumb}{3}{\@tempnuma}%
    %% \ifnum\@tempnuma=\z@
    %%   \@tempcntb\@ne\relax
    %% \fi
    %%output
    \ifnum \@tempcntb=\z@
      {\NabeAzz@font \number\@tempcnta}%
    \else
      {\NabeAzz@h@font \number\@tempcnta}%
    \fi
    \space
  }%
}

\newcount\@temp@m@dcntp
\newcount\@temp@m@dcntq
\newcount\@temp@m@dcntr

\def\@m@d#1#2#3{%%#1 % #2, return #3
  \@temp@m@dcntq#1\relax
  \@temp@m@dcntr#2\relax
  \ifnum \@temp@m@dcntq=\@temp@m@dcntr \@temp@m@dcntq\z@\relax\fi
  \@whilenum \@temp@m@dcntq>\@temp@m@dcntr \do{%
    \@temp@m@dcntp\@temp@m@dcntq\relax
    \divide \@temp@m@dcntp by \@temp@m@dcntr    % q = a/b
    \multiply \@temp@m@dcntp by -\@temp@m@dcntr %
    \advance \@temp@m@dcntp by \@temp@m@dcntq   % r = -bq + a
    \@temp@m@dcntr\@temp@m@dcntq\relax
    \@temp@m@dcntq\@temp@m@dcntp\relax          % q <-> r
  }%
  \edef#3{\the\@temp@m@dcntq}\relax
}

\def\chk@h@thr@@#1#2{%#1: integer < 10^{8}
  \edef\chk@h@thr@@@temp{\@ight@digits{#1}}%
  \expandafter\@chk@h@thr@@@ight@digits\chk@h@thr@@@temp{#2}}
\def\@chk@h@thr@@@ight@digits#1#2#3#4#5#6#7#8#9{%
  \ifnum #1=\thr@@ \def#9{\@ne}\relax \else
   \ifnum #2=\thr@@ \def#9{\@ne}\relax \else
    \ifnum #3=\thr@@ \def#9{\@ne}\relax \else
     \ifnum #4=\thr@@ \def#9{\@ne}\relax \else
      \ifnum #5=\thr@@ \def#9{\@ne}\relax \else
       \ifnum #6=\thr@@ \def#9{\@ne}\relax \else
        \ifnum #7=\thr@@ \def#9{\@ne}\relax \else
         \ifnum #8=\thr@@ \def#9{\@ne}\relax \else
          \def#9{\z@}\relax
         \fi\fi\fi\fi\fi\fi\fi\fi}

\def\sum@@ight@digits#1#2{%#1: integer < 10^{8}
  \edef\sum@@ight@digits@temp{\@ight@digits{#1}}%
  \expandafter\@sum@@ight@digits\sum@@ight@digits@temp{#2}}
\newcount\@sum@@ight@digitscnt
\def\@sum@@ight@digits#1#2#3#4#5#6#7#8#9{%
  \@sum@@ight@digitscnt#1\relax
  \advance \@sum@@ight@digitscnt #2
  \advance \@sum@@ight@digitscnt #3
  \advance \@sum@@ight@digitscnt #4
  \advance \@sum@@ight@digitscnt #5
  \advance \@sum@@ight@digitscnt #6
  \advance \@sum@@ight@digitscnt #7
  \advance \@sum@@ight@digitscnt #8
  %%
  \edef#9{\the\@sum@@ight@digitscnt}\relax
}

\def\@ight@digits#1{%
  \ifnum#1<10000000 0\fi
  \ifnum#1<1000000  0\fi
  \ifnum#1<100000   0\fi
  \ifnum#1<10000    0\fi
  \ifnum#1<1000     0\fi
  \ifnum#1<100      0\fi
  \ifnum#1<10       0\fi
  \number#1}

%%from /usr/share/texmf-dist/tex/latex/base/ot1cmfr.fd
\DeclareFontFamily{OT1}{cmfr}{\hyphenchar\font45 }
\DeclareFontShape{OT1}{cmfr}{m}{n}{%
      <->cmff10%
    }{}
\DeclareFontShape{OT1}{cmfr}{m}{it}{%
      <->cmfi10%
    }{}

\makeatother

\begin{document}
\noindent
\NabeAzz{99999}%% OK
%\NabeAzz{200000}%% OK
%\NabeAzz{999999}%%!!Fatal error occurred, no output PDF file produced!
\end{document}

\NabeAzz のアルゴリズムとおおざっぱなコードは、30 分くらいでできていたのですが、マクロの名前を整えたり、マクロの仕様をちょっと変えたり、「3 の倍数」判定法を試したり、time で時間を測ったり…など、色々とやっているうちに、3 時間にもなってしまいました (^^;;

ほかのアプローチやもっときれいなマクロを書けるかもしれませんが、\NabeAzz に関して、わたくしはひとまずこれくらいにしておきます。もっと美しいコード例がありましたら、ぜひとも教えてくださいませ。

いくつかいいわけを。

  • ZR さんの出題では、アホになる数字の場合に 12pt にせよという指示がありましたが、わたしは 14pt にしました。ZR さんは品がございますので、12pt になさっておられるのだと思いますが、やはりアホな数字ですから、ふざけてみました。
  • cmfr のついては、ot1cmfr.fd からとってきて、<-> にして逃げました。

「3 の倍数」判定法にしてみる

「3 の倍数」判定法は、「整数 n が 3 で割りきれるための必要十分条件は、n の各桁の数字の総和が 3 で割りきれる。」なのですが、これを \NabeAzz で使ってみると、直接 mod 3 するよりも、計算時間が増えました。手元の計算機環境と実行結果は以下のとおりです。

CPU Intel Core 2 Duo E8500 3.16GHz
MEM PC6400 DDR2 SDRAM 8GB (2GBx4)
OS Vine Linux 6.0 x86_64
TeX 環境 Vine Linux 6 の TeX Live 2009
$ pdflatex --version
pdfTeX 3.1415926-1.40.10-2.2 (Web2C 2009)
kpathsea version 5.0.0
Copyright 2009 Peter Breitenlohner (eTeX)/Han The Thanh (pdfTeX).
There is NO warranty.  Redistribution of this software is
covered by the terms of both the pdfTeX copyright and
the Lesser GNU General Public License.
For more information about these matters, see the file
named COPYING and the pdfTeX source.
Primary author of pdfTeX: Peter Breitenlohner (eTeX)/Han The Thanh (pdfTeX).
Compiled with libpng 1.2.46; using libpng 1.2.46
Compiled with zlib 1.2.5; using zlib 1.2.5
Compiled with poppler version 0.16.6

$ time pdflatex NabeAzz.tex

\NabeAzz{99999}
Case 2-1: 
real    0m8.016s    0m7.789s    0m7.946s
user    0m7.902s    0m7.619s    0m7.821s
sys     0m0.028s    0m0.039s    0m0.039s

Case 2-2: 
real    0m8.086s    0m8.320s    0m8.165s
user    0m7.965s    0m8.206s    0m8.073s
sys     0m0.034s    0m0.028s    0m0.028s

\NabeAzz{200000}
Case 2-1: 
real    0m42.150s    0m42.134s    0m42.315s
user    0m41.870s    0m41.858s    0m42.061s
sys     0m0.097s     0m0.105s     0m0.117s 

Case 2-2: 
real    0m57.169s    0m57.478s    0m56.738s
user    0m56.921s    0m57.129s    0m56.496s
sys     0m0.109s     0m0.124s     0m0.084s 

追記:あべのりさんに触発されて、\NabeAzz を変えてみた

あべのりさんが にっき♪ で、\NabeAzz をなんか適当やっていた*1。たしかに mod N を作らなければ、数行で終わりますね!

というわけで、上記の \@ight@digits を使わずに*2、区切り文字を入れて \ifx で順番に調べる方法に変えてみた*3。以下では \@m@d を、相変わらず使っているが、そこはご愛嬌ということで。

%#!pdflatex NabeAzz
%#LPR acroread NabeAzz.pdf
\documentclass{article}

\makeatletter
\newif\if@NabeAzz \@NabeAzzfalse

\def\NabeAzz@font{%
  \if@NabeAzz \usefont{OT1}{cmfr}{m}{it}\Large\else\normalfont\fi}

\def\NabeAzz#1{%
  \@tempcnta\z@\relax%%loop counter
  \@whilenum \@tempcnta<#1 \do{%
    \advance\@tempcnta\@ne
    %%case 1
    \expandafter\chk@NabeAzz\number\@tempcnta E%
    %%case 2
    \@m@d{\@tempcnta}{3}{\@tempnuma}%
    \ifnum\@tempnuma=\z@ \@NabeAzztrue\fi
    %%output
    {\NabeAzz@font \number\@tempcnta}%
    \space
  }%
}

\newif\if@NabeAzzEnd
\def\chk@NabeAzz#1{%
  \@NabeAzzfalse
  \@NabeAzzEndfalse
  \@chk@NabeAzz#1
}
\def\@chk@NabeAzz#1{%
  \@@chk@NabeAzz#1%
  \if@NabeAzzEnd\else\expandafter\@chk@NabeAzz\fi
}
\def\@@chk@NabeAzz#1{%
  \def\@tempa{#1}%
  \def\@tempb{E}%
  \ifx\@tempa\@tempb
    \@NabeAzzEndtrue
  \else
    \def\@tempb{3}%
    \ifx\@tempa\@tempb\@NabeAzztrue\fi
  \fi
}

\newcount\@temp@m@dcntp
\newcount\@temp@m@dcntq
\newcount\@temp@m@dcntr

\def\@m@d#1#2#3{%%#1 % #2
  \@temp@m@dcntq#1\relax
  \@temp@m@dcntr#2\relax
  \ifnum \@temp@m@dcntq=\@temp@m@dcntr \@temp@m@dcntq\z@\relax\fi
  \@whilenum \@temp@m@dcntq>\@temp@m@dcntr \do{%
    \@temp@m@dcntp\@temp@m@dcntq\relax
    \divide \@temp@m@dcntp by \@temp@m@dcntr    % q = a/b
    \multiply \@temp@m@dcntp by -\@temp@m@dcntr %
    \advance \@temp@m@dcntp by \@temp@m@dcntq   % r = -bq + a
    \@temp@m@dcntr\@temp@m@dcntq\relax
    \@temp@m@dcntq\@temp@m@dcntp\relax          % q <-> r
  }%
  \edef#3{\the\@temp@m@dcntq}\relax
}

%%from /usr/share/texmf-dist/tex/latex/base/ot1cmfr.fd
\DeclareFontFamily{OT1}{cmfr}{\hyphenchar\font45 }
\DeclareFontShape{OT1}{cmfr}{m}{n}{%
      <->cmff10%
    }{}
\DeclareFontShape{OT1}{cmfr}{m}{it}{%
      <->cmfi10%
    }{}
\makeatother

\begin{document}
\noindent
\NabeAzz{9999}
\end{document}

「3 の倍数と 3 が付く数字のときだけアホになります」wikipedia:渡辺鐘 でウケそうなのは、40 番までだと思う件

このアホになる数字の列を見て、40 番以降がツラいですねー。このあと、アホになる数字が 130 番台までまばらになるので、とたんにつまらなくなります。そのつぎに大きな山があるのが 300 番台ですが、さすがに 300 番台までこの芸を続けないでしょう。

3000 番台になると、もうアホになる数字ばっかりですよね\<(w

謝辞

というわけで、ZR さんの「\NabeAzz を作れ!」には、たいへん楽しませもらいました。ありがとうございました。

つぎは、Postscript で NabeAzz を(おぃ

*1:"適当" というのは、「適切」という意味ですヨ。

*2:実は、\@ight@digits っぽい処理って、「11」を「十一」とか、こーゆーときに使った以来だったんで、感慨深かったりしておりました。

*3:この方法だと、N が 10^{8} 以上のときも対応できる。しかし、N = 300000 くらいで、\NabeAzz{N} がすでに Fatal error occurred になるわけだが\<(w