Brainf*ckで九九表に載っているか判定するプログラム

TSG Advent Calendar 2019 18日目です。

17日目:

adventar.org

Brainf*ck Advent Calendar 2019 18日目です。

17日目:

adventar.org

まえがき

Szkieletorと申します。

Brainf*ckという変わった言語でのプログラミングに初挑戦したので、記録を残しておきます。

いきなりですがちょっと脱線して、きっかけについて。

学祭とTSG LIVE!とコードゴルフ

私が所属するサークルTSGでは、"TSG LIVE!"という「ライブプログラミングショー」を学祭でやっており、その中の一つに「ライブコードゴルフ大会」というものがあります。

コードゴルフは簡単に言うと「コード長をできるだけ縮める」という遊びです。TSG LIVE!では、言語が書かれたマスを陣取りで取り合います。

特徴的なのは、RubyやC、Pythonといった汎用言語だけでなく、Brainf*ckを始めとしたesolang(難解プログラミング言語)のマスがいくつもあるという点です。

今回五月祭・駒場祭の両方でこのコードゴルフ大会に少しだけ関わって、なにかesoを書いてみたいなあと思い、一番有名なのはBrainf*ckだな! ということで書いてみることにしました。

ちなみにコードゴルフ大会は動画があがっています。

www.youtube.com

  • 五月祭Day2

www.youtube.com

  • 駒場祭(これもうらさんがプレイヤー)

www.youtube.com

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に初挑戦して完成させるまでの記録」として明日書こうと思います。

明日の記事:

twitter.com