Brainfuckの教材をみていると度々Hello world
からスタートをするものをいますが、Hello world
ではBrainfuckの,
を除く全ての命令を使うため、比較的難易度が高いです。
そのため、簡単な命令からそれぞれの命令を確認し、より理解しやすいルートを用意しました。
Brainfuckを超簡単なコードから始める
lesson01 入出力を学ぶ
あなたがどんなに優れたプログラマーであってもその成果を他人に理解できる形で提示できなければ意味がありません。
同様に、どれだけ素晴らしいプログラムであっても操作できなければ使用者は退屈を感じるでしょう。
そのような悩みを解決するのが入出力命令です。
これはBrainfuckで最小の(そして意味のある)プログラムを書くのに必須です。
ここではBrainfuckで書くことができる最も簡単なプログラムである1文字のechoプログラムを考えてみましょう。
これはA
と入力したらA
と出力されるようなプログラムです。
1文字のechoプログラムは以下のような流れで行われます
- 1文字の入力を受け付ける
- 入力された値を出力する
それぞれはBrainfuckでは,
命令と.
命令です。
,.
配列の状態は以下のように変化します。
|00|00|00|...
^,(入力←A)
|41|00|00|...
^.(出力→A)
2文字のechoプログラムは同様に
,.,.
となります。
各自で状態遷移を確認してみてくださいね。
lesson02 データの退避を学ぶ
さて、次は任意の2文字を入力した後、入力された2文字を逆順に表示するようなプログラムを考えましょう。
例えばAB
と入力された場合BA
と出力されるようなプログラムです。
それではさっきと同じようにプログラムを流れを考えていきましょう。
- 1文字目を入力
- 2文字目を入力
- 2文字目を出力
- 1文字目を出力
「なんださっきとあまり変わらないじゃないか!」
,,..
……としてしまうと大変です。
上の例ではAB
と入力してもBB
と出力されてしまいます。
それはなぜでしょうか。
|00|00|00|...
^,(入力←A)
|41|00|00|...
^,(入力←B)
|42|00|00|...
^ *おっと*
配列の状態遷移を見てみるとA
という情報が入ってた場所にB
という情報を上書きしてしまっていることがわかります。
このような場合、同じ場所に情報を上書きをしないよいうにポインタを移動させてみましょう。
|41|00|00|...
^,(入力←B)
|41|42|00|...
^ *ヨシ!*
このようにポインタを移動させることでデータの上書きを防ぐことができます。
それではポインタの移動を適用したコードを考えてみましょう。
ポインタの移動では>
と<
を使うことで実現できます。
- ,,..
+ ,>,.<.
配列は以下のように状態が遷移します。
|00|00|00|...
^,(入力←A)
|41|00|00|...
^>
|41|00|00|...
^,(入力←B)
|41|42|00|...
^.(出力→B)
|41|42|00|...
^<
|41|42|00|...
^.(出力→A)
lesson03 加減算を学ぶ
さて、ある任意の1文字が入力された後、入力された文字の次の文字を出力した後、入力された文字の前の文字を出力するようなプログラムを考えます。
例えば1
と入力された場合は20
と出力するようなプログラムです。
これに関しては簡単なので説明は不要かもしれません。
,+.--.
配列は以下のように状態が遷移します。
|00|00|00|...
^,(入力←1)
|31|00|00|...
^+
|32|00|00|...
^.(出力→2)
|32|00|00|...
^--
|30|00|00|...
^.(出力→0)
lesson04 条件分岐、ループについて学ぶ
さて、Brainfuckを学ぶ上で高確率で躓くポイントの登場です。
echoプログラム
改行が入力されるまでに入力された文字列を出力するプログラムを考えてみましょう。
例えばABC\n
と入力したらABC\n
と出力されるようなプログラムを考えましょう。
ただし改行\n
はBrainfuckでは0A
、十進数では10
です。
さて、プログラムの流れを考えてみましょう。
- 1文字を入力する
- 1文字を出力する
- 入力された文字が
\n
ならプログラムを終了。それ以外なら1.
に戻る
さて、ここで1.
と3.
に注目してください。
ここでループになっていることがわかりますね。
さらにその中で入力と出力を行なっています。
それを反映したコードは以下のようになるでしょう。
[,.]
「よし!これであとは改行で終了するだけだな!」
って人は気をつけてください。
|00|00|00|...
^[
これは肝心のループに入る時の配列の状態ですが、ポインタの指す値が00
となっており、[
は対応する]
にジャンプしてしまいループに入らないのです。
だからポインタの指し示す値を00
以外にする必要があったんですね。
- [,.]
+ +[,.]
さて、残すは改行でプログラムを終了するだけですがそれも簡単です。
改行\n
は0A
であり十進数で10
です。
すなわち、改行から0A
だけ値を引けばポインタの指す値は00
になり、]
に対応した[
に飛んだ直後に対応した]
へと飛んでくれるでしょう。
- +[,.]
+ +[,.----------]
これによって改行が入力されるまでに入力された文字列を出力するechoプログラムが書けました。
逆順出力
それでは改行されるまで文字を入力し、改行を除く入力された文字列を逆順に表示するようなプログラムを作りましょう。
例えばABCDEF\n
と入力されたFEDCBA
と出力されるようなプログラムです。
さて、プログラムの流れを考えてみましょう。
- 前の文字のデータに上書きしないようにポインタを移動する
- 1文字を入力する
- 入力された文字が
\n
ならプログラムを終了。それ以外なら1.
に戻る
この流れをコードで書くとこのようになるでしょう。
あ、ループに入るための最初の+
も忘れないでくださいね。
+[>,----------]
さて、これで入力の方は大丈夫でしょう。
このコードでABCDEF\n
を入力した場合の終了時はこのような状態になっているでしょう。
|01|37|38|39|3A|3B|3C|00|...
^
それでは逆順で出力をしていくようなコードを書いていきましょう。
現状は上のような状態なのでとりあえずポインタを一つ左動かしたとして考えましょう。
|01|37|38|39|3A|3B|3C|00|...
^
さて、ここで入力された文字のデータは00
ではない事に注目して下さい。
つまり、Brainfuckの配列の値が00
ではない限りは入力された文字だと言えそうですね。
「ん?」
と思った方は勘がいいです。
配列の先頭を見てみてください。
配列の先頭の値にはループに入るために+
が行われて01
が入ってしまってます。
これではポインタの指す値が00
ではない限り繰り返すと言った処理ができそうにありません。
それでは、どうしたら良いでしょうか。
答えは簡単です。
先程提示したこちらのコード。
+[>,----------]
こちらのループの入り口に-
を置いて結果的に配列の先頭の値が00
になるようにしたら良いのです。
「そんなことしたら他の部分にも影響が出るのでは?」
と思うかもしれませんが、出ます。
Brainfuckとはそういう言語です。
一つの変更が後々響いてきたりします。
とりあえず先程の説明を適応したコードを見てみましょう。
- +[>,----------]
+ +[->,----------]
それではこのコードが終わった時の配列の状態を確認してみましょう。
|00|36|37|38|39|3A|3B|00|...
^
さて、それを踏まえて出力部分のプログラムの流れを考えてみましょう。
- 入力された情報を出力
- ポインタを一つ左に移動
- ポインタの指す値が
00
ではない場合、1.
に戻る。
これを反映したコードは以下のようになるでしょう。
+[->,----------]
<
[.<]
しかしながら、私はこれまで
- ループを終了するための改行を判定するための10減算
- ループに入るための値を相殺するための1減算
を行なってきました。
つまり、入力された文字の情報に対して合計で11減算していました。
これをそのまま出力すると入力された文字の11文字前を出力されてしまうでしょう。
それでは入力された文字を正しく出力するときはどうしたら良いでしょうか。
これに関しては深く考える必要はなく出力の直前に減算したのと同じ数、11だけ加算をしたら良いでしょう。
+[->,----------]
<
- [.<]
+ [+++++++++++.<]
さて、上のコードで一応の目的は達成できてはいますが、もっとコードを短くすることはできそうです。
それはどこでしょうか。
ヒントは前半です。
答え
最初のループに入るための数値調整を-
にすることで合計で2文字削減することができます。
- +[->,----------]<[+++++++++++.<]
+ -[+>,----------]<[+++++++++.<]
以上でBrainfuckの基礎的な説明は終了です。