漢数字で九九の表を出力するバッチファイル(どう書く?orgの課題)

Webサイトを巡っていておもしろいサイトを発見。

screenshot

どう書く?org」へようこそ!このサイトは出されたお題をいかに解くか競い合う、プログラマのためのコロシアムです。投稿を試してみたい方はテスト、とりあえず眺めてみたい方は言語の一覧 がおすすめです。


ここに興味ある課題が。
漢数字で九九表を出力するプログラムを作るのだが、条件としてプログラムソース中でアラビア数字を使えないという課題だ。

漢数字で九九の表

漢数字で九九の表 syat #7820(2008/10/23 07:42 GMT) [ Other ] 評価7/7=1.00

漢数字で九九の表を作ってください。
ただし以下の条件をつけます。

条件
一.アラビア数字(0〜9)禁止。
  プログラムにも出力結果にもアラビア数字を含んではいけない。(全角・半角とも)
二.結果の数字は、「七」とか「一○」(=10)とか「六四」(=64)のような形式とする。
三.九九の結果をそのままプログラム中に書き込んではいけない。


出力例

 一  二  三  ・・・・
 二  四  六  ・・・・
 三  六  九  ・・・・
 四  八 一二
 ・
 ・

http://ja.doukaku.org/212/

いろんな言語でチャレンジしている人がいる。まぁ、普通の言語だと文字列操作の関数とか列挙型などが充実しているから作り易いだろうと思いつつ、こうなるとどうしてもバッチファイルで作りたくなる。

バッチファイル中でアラビア数字を使わないとなると、%1などの引数も使えないので call :sub 形式は無理だなぁと漠然と思う。まずはとにかく0〜10の数値を変数に入れる手段を考えた。

考えついたのがfor文のファイルセットに対する%変数からファイルサイズを求める%~z変数の記述を使うこと。

次に、ファイルサイズが0バイト〜10バイトのファイルを作る方法。
0バイトのファイルを作るのは、type NUL>filename でよく知られている。
2バイトのファイルは echo.>filename で改行(CRLF)の2バイトのファイルができあがる。
問題は1バイトのファイルを作る方法。いろいろ試行錯誤した結果、

type nul>〇
copy /a 〇 一>NUL

とすると0バイトのファイル"〇"に対して Ctrl-Z が付加された"一"というファイルができあがることが判った。
3〜10バイトのファイルを作るのはecho 文で簡単にできる。

次に九九なので順当にfor文の二重ループ。
括弧を使った複合文は嫌いなのだが、call :sub 形式が使えないのでしようがない。九九の計算はfor文でファイルセットに〇 一 二 … 九 をならべてそのファイルサイズ %%~zx , %%~zy を使えば数値を取り出せると目星をつけた。
あとは、10の位と1の位に分けるために、%十% という固定値を使う。これはあらかじめファイルサイズから環境変数"十"に数値を代入したものを使うことにした。
最後に10の位と1の位にわけた数値を漢数字に戻す。ここでもfor文のファイルセットとファイルサイズを比較して一致したファイル名=漢数字となることを利用した。ここで!a!や!b!という環境変数の遅延展開を使った(遅延展開の記述は嫌いなんだが)。
最後は段毎に整形して終わり。
できたバッチファイルはこれ。

echo off
setlocal ENABLEDELAYEDEXPANSION

type nul>〇
copy /a 〇 一>NUL
echo.>二
echo ->三
echo -->四
echo --->五
echo ---->六
echo ----->七
echo ------>八
echo ------->九
echo -------->十

for %%i in (十) do set %%i=%%~zi

for %%x in (一 二 三 四 五 六 七 八 九) do (
   for %%y in (一 二 三 四 五 六 七 八 九) do (
      set /a a= %%~zx * %%~zy  /  %十%
      set /a b= %%~zx * %%~zy  %% %十%
      set ans_a= 
      for %%i in (一 二 三 四 五 六 七 八)       do if !a! EQU %%~zi set ans_a=%%i
      for %%i in (〇 一 二 三 四 五 六 七 八 九) do if !b! EQU %%~zi set ans_b=%%i
      set line=!line! !ans_a!!ans_b!
    )
    echo !line! & set line=
)

for %%i in (〇 一 二 三 四 五 六 七 八 九 十) do del %%i

この後見直して、数値→漢数字への変換部分は call set 〜を使えば環境変数の副文字列(部分文字列)を取り出すことができると気がついた。これなら総当たりのループが不要になる。
そのバージョンのバッチファイルは以下のようになった。

echo off
setlocal ENABLEDELAYEDEXPANSION

type nul>〇
copy /a 〇 一>NUL
echo.>二
echo ->三
echo -->四
echo --->五
echo ---->六
echo ----->七
echo ------>八
echo ------->九
echo -------->十

set str=〇一二三四五六七八九

for %%i in (一 十) do set %%i=%%~zi

for %%x in (一 二 三 四 五 六 七 八 九) do (
   for %%y in (一 二 三 四 五 六 七 八 九) do (
      set /a a= %%~zx * %%~zy  /  %十%
      set /a b= %%~zx * %%~zy  %% %十%
      call set ans_a=%%str:~!a!,%一%%%
      call set ans_b=%%str:~!b!,%一%%%
      set ans_a=!ans_a:〇= !
      set line=!line! !ans_a!!ans_b!
    )
    echo !line! & set line=
)

for %%i in (〇 一 二 三 四 五 六 七 八 九 十) do del %%i

ソースの見た目は最初に作った方が好きだけれど、速度的にはこちらのほうが早いようだ。


更にもう他に改良の余地はないかと考えてみた。
数値を変数に入れるのに、以前 set コマンドで設定されている変数が使えないかと眺めていたことを思い出してもう一度見てみると、NUMBER_OF_PROCESSORSというのがある。以前も気づいていたのだが、「プロセッサの数だったら環境によって1だったり2だったりするよなぁ」とそこで思考停止していた。よく考えたら割り算すればこの変数から数値の1が作れる!数値の1から他の数値は計算で求められるなと気づいて、ファイルサイズではなく環境変数の数値を使うバージョンのバッチファイルを作ってみた。
ここではループもファイルサイズではなく変数の数値を使うので for /L を使っている。

echo off
setlocal ENABLEDELAYEDEXPANSION

set /a 一= NUMBER_OF_PROCESSORS / NUMBER_OF_PROCESSORS
set /a 九= (一 + 一 + 一) * (一 + 一 + 一)
set /a 十= 九 + 一 

set str=〇一二三四五六七八九

for /L %%x in (%一%,%一%,%九%) do (
   for /L %%y in (%一%,%一%,%九%) do (
      set /a a= %%x * %%y  /  %十%
      set /a b= %%x * %%y  %% %十%
      call set ans_a=%%str:~!a!,%一%%%
      call set ans_b=%%str:~!b!,%一%%%
      set ans_a=!ans_a:〇= !
      set line=!line! !ans_a!!ans_b!
    )
    echo !line! & set line=
)

このバッチファイルはファイルを作らない分更に早いようだ。だいぶ短くなった。

だが個人的にはやはり最初に作ったあやしげなバージョンが好きである。数値を求めるために漢数字ファイル名のファイルを作るという馬鹿馬鹿しさ。これこそパチもんなバッチファイルだと思う。