\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 の実装そのもの」は除外する。
http://d.hatena.ne.jp/zrbabbler/20110815/1313398638
さて、あなたは TeX を使えますか……?
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 番台までこの芸を続けないでしょう。
謝辞
というわけで、ZR さんの「\NabeAzz を作れ!」には、たいへん楽しませもらいました。ありがとうございました。
つぎは、Postscript で NabeAzz を(おぃ