TSG Advent Calendar 2019 18日目です。
17日目:
Brainf*ck Advent Calendar 2019 18日目です。
17日目:
まえがき
Szkieletorと申します。
Brainf*ckという変わった言語でのプログラミングに初挑戦したので、記録を残しておきます。
いきなりですがちょっと脱線して、きっかけについて。
学祭とTSG LIVE!とコードゴルフ
私が所属するサークルTSGでは、"TSG LIVE!"という「ライブプログラミングショー」を学祭でやっており、その中の一つに「ライブコードゴルフ大会」というものがあります。
コードゴルフは簡単に言うと「コード長をできるだけ縮める」という遊びです。TSG LIVE!では、言語が書かれたマスを陣取りで取り合います。
特徴的なのは、RubyやC、Pythonといった汎用言語だけでなく、Brainf*ckを始めとしたesolang(難解プログラミング言語)のマスがいくつもあるという点です。
今回五月祭・駒場祭の両方でこのコードゴルフ大会に少しだけ関わって、なにかesoを書いてみたいなあと思い、一番有名なのはBrainf*ckだな! ということで書いてみることにしました。
ちなみにコードゴルフ大会は動画があがっています。
- 五月祭Day1(実況をやりました。アドベントカレンダーに寄稿しているうらさんが駒場チームのプレイヤーをしています)
- 五月祭Day2
- 駒場祭(これもうらさんがプレイヤー)
Brainf*ckとは?
TSG LIVE! ライブコードゴルフ大会で定番になりつつある言語でもあります。
駒場祭の前に各esolangでTwitterで検索をかけたんですが、Brainf*ckはesoの中でもにぎわいがあっていいですね。
今回のお題
駒場祭2019 ライブコードゴルフ大会で出題されたものと同じ、九九判定のプログラムを書きました。
お題:2桁の整数が100個改行区切りで与えられるので,それぞれについて九九表に存在する数のとき1,そうでないとき0を出力せよ
上のリンクに詳しいルール、入出力例等があります。安心してゴルフしてください。
やったこと
Pythonでプログラムを書く
汎用言語で書いてそれをアルゴリズムそのままにBrainf*ckに移植すればいいんじゃないかな、と思い、まずPythonで書きました。
ちなみに私はPythonもほぼ書いたことがありません。for-elseなどググりながら書きました。
for i in range(100): number = int(input()) for j in range(10): for k in range(10): if number == j*k: print "1", break else: continue break else: print "0",
単純に、九九を1から81まで計算し、入力と比較して合っていたら二重ループを抜けて"1"を出力、81までのどれともマッチしなかったら"0"を出力、という方針です。
ゴルフ的にはもっとよいアルゴリズムがあると思いますが、未知の言語に移植するということを念頭に置いて、簡単そうだという理由で愚直な方法を。
あとは移植だ! ということで、ところどころ方針を変えつつBrainfuckを書いていきます。
できたBrainf*ckプログラム
先に完成したコードを見せておきます。
>++++++++++[<++++++++++>-]<[>,>,>++++++++++++++++++++++++++++++++++++++++++++++++[->+>+<<]>[-<<<->>>]>[-<<<->>>]<<<>++++++++++<<[->>[-<+>]++++++++++<<]>>----------<<>>>+++++++++>+++++++++<[>[<[->>+>+<<<]>>[-<<+>>]<[->+>>+<<<]>>>[-<<<+>>>]<<[->>+<<]>[-<>>[->+>+<<]<<>>>>[-<<+>>]<<<<>>>[-<<<+>>>]<<<>]<>>[-]<<<<<<[->>>>>>>>>+>+<<<<<<<<<<]>>>>>>>>>>[-<<<<<<<<<<+>>>>>>>>>>]<[-<<<<<->>>>>]+<<<<<[>>>>>-<<<<<[-]]>>>>>>>[>+>+<<-]>[<+>-]+>[<->[-]]<[<<<[->>+<<]>>>-]<<<<<<<<<-]+++++++++<-]>[-]<>>>>>>>>>>++++++++++++++++++++++++++++++++++++++++++++++++[-<+>]<.[-],[-]<<<<<<<<<<<<<-]
……はい。訳が分からないと思うので、コメント付きのプログラムも載せますね。
ちなみに処理系は apt
で入れたbfを使いました。
> +++++ +++++ initialize cell #1 to 10 [ use loop to set 100 to cell #0 < +++++ +++++ add 10 to cell #0 > - ] < [ > , store the input (10th place) to cell #1 > , store the input (1st place) to cell #2 > +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++ set 48 [->+>+<<] clone to #4 and #5 >[-<<<->>>] #1 sub #4 >[-<<<->>>] #2 sub #5 <<< move #2 > +++++ +++++ set 10 to cell #3 <<[->>[-<+>]++++++++++<<] compute decimal and store it to cell #2 >>----------<< >>> +++++ ++++ set 9 to cell #4 > +++++ ++++ set 9 to cell #5 < move #4 [> [ < move #4 [->>+>+<<<] #6:7 add #4 >> move #6 [-<<+>>] #4 add #6 < move #5 [->+>>+<<<] #6/#8 add #5 >>>[-<<<+>>>] #5 add #8 << move #6 [->>+<<] #8 add #6 >[-< while #7 do {decr #7 >>[->+>+<<]<< #9/#10 add #8 >>>>[-<<+>>]<<<< #8 add #10 >>>[-<<<+>>>]<<< #6 add #9 >]< end and prod stored to #6 >>[-]<< delete #8 <<<<[->>>>>>>>>+>+<<<<<<<<<<] #11/#12 add #2 >>>>>>>>>>[-<<<<<<<<<<+>>>>>>>>>>] #2 add #12 <[-<<<<<->>>>>]+<<<<<[>>>>>-<<<<<[-]] #11=#11==#6 >>>>>>> move #13 [>+>+<<-]>[<+>-]+ if(#13) >[<->[-]] (#13=1 so do nothing) <[ <<<[->>+<<] (#13=0 so #13 add #11) >>>- ] <<<<<<<<<-] move #5 and decrement +++++ ++++ set 9 in #5 <-] decrement #4 >[-]< reinitialize #5 >>>>>>>>> move #13 > +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++ set 48 to #14 [-<+>] #13 add #14 <. output #13 [-],[-] set 0 to #13 and read newline <<<<<<<<<<<<< back to #0 - ]
#がついているものはセルの番号です。
手順の解説
単体で説明されても理解できないかと思いますが、デバッガ(これとか)で過程を一緒に目で追うとわかるかと。
1. 入力が100行のため100回ループを回す準備をする
> +++++ +++++ initialize cell #1 to 10 [ use loop to set 100 to cell #0 < +++++ +++++ add 10 to cell #0 > - ] <
2.100回のループに入る。入力(#1, #2)を文字列から整数に変換する
1バイトずつしか読み込んでくれず、さらに文字列になるため、変換する必要があります。変換後の整数、すなわち入力をxとします。
文字(1~9)から数字への変換は、48(ASCIIコードの0)を引くとできます。
[ > , store the input (10th place) to cell #1 > , store the input (1st place) to cell #2 > +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++ set 48 [->+>+<<] clone to #4 and #5 >[-<<<->>>] #1 sub #4 >[-<<<->>>] #2 sub #5 <<< move #2 > +++++ +++++ set 10 to cell #3 <<[->>[-<+>]++++++++++<<] compute decimal and store it to cell #2 >>----------<<
3.九九を1個ずつ計算する
まず、9をふたつセット(#4, #5)。
>>> +++++ ++++ set 9 to cell #4 > +++++ ++++ set 9 to cell #5
4.2重ループに入り、二つの数字それぞれを、ループを回す用と掛け算用にコピーして増やす。
#6と#7を掛け算のために使うことにします。
< move #4 [> [ < move #4 [->>+>+<<<] #6:7 add #4 >> move #6 [-<<+>>] #4 add #6 < move #5 [->+>>+<<<] #6/#8 add #5 >>>[-<<<+>>>] #5 add #8
5.掛け算をする
積をyとします。
#8,#9,#10を一時的に使います。
<< move #6 [->>+<<] #8 add #6 >[-< while #7 do {decr #7 >>[->+>+<<]<< #9/#10 add #8 >>>>[-<<+>>]<<<< #8 add #10 >>>[-<<<+>>>]<<< #6 add #9 >]< end and prod stored to #6 >>[-]<< delete #8
6.x=(x==y) xとyが等価かどうか比較し、その値を#11に入れる
#2の値(input)は保存する必要があるため、コピーして二つに増やしています。
<<<<[->>>>>>>>>+>+<<<<<<<<<<] #11/#12 add #2 >>>>>>>>>>[-<<<<<<<<<<+>>>>>>>>>>] #2 add #12 <[-<<<<<->>>>>]+<<<<<[>>>>>-<<<<<[-]] #11=#11==#6
7.#13を結果を入れるセルとし、これまで0でいま1になった場合のみ書き換える
Pythonで書いたようないわゆる break
の方法が思いつかず、ループを81周しています。ただ書き換えるだけだと#13が1になったあとで0で書き換えられる事態が発生するので、条件分岐させています。
>>>>>>> move #13 [>+>+<<-]>[<+>-]+ if(#13) >[<->[-]] (#13=1 so do nothing) <[ <<<[->>+<<] (#13=0 so #13 add #11) >>>- ]
8.ループ変数をデクリメント
<<<<<<<<<-] move #5 and decrement +++++ ++++ set 9 in #5 <-] decrement #4
9.#13にあわせて0か1を出力
文字列に変換する必要があるため、今度は48を足しています。48を作るのは手抜きしました。
>[-]< reinitialize #5 >>>>>>>>> move #13 > +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++++ +++ set 48 to #14 [-<+>] #13 add #14 <. output #13 [-],[-] set 0 to #13 and read newline <<<<<<<<<<<<< back to #0 - ]
できました。574byteです。
あとがき
実際はこれをスムーズに書けたわけではなく、あちこちつまづきながら苦しんで実装していました。記事が長くなってしまったので、それは「Brainfuckに初挑戦して完成させるまでの記録」として明日書こうと思います。
明日の記事: