Brainf*ckで条件分岐をする(パート1)

過去のブログのアーカイブ
この記事は前身のブログのアーカイブを引き継いだものです. 画像が正しく表示できないなど,コンテンツの表示に問題がある恐れがあります.

Brainf*ckで条件分岐をする方法の紹介。まずは基本的な条件分岐の実装をしてみます。

Brainf*ckについて

言語仕様についてはこちらでも紹介しています。よろしければ参考にどうぞ。

Brainf*ckの仕様と動作

2016.10.17

単語について

ここでは、メモリの値を#1, #2のように紹介しています。
例えば、メモリの7番目を表すときは#7みたいな感じで表します。
また、ポインターの移動については# = 2のように表します。
例えば、ポインターの位置を5にするときは# = 5みたいな感じで表します。
また、コードはC#(っぽいやつ)と比較しつつ紹介しています。コメント内のコードがBrainf*ckコードです。

Brainf*ckでの条件分岐の特徴

Brainf*ckで条件分岐を実現するには [ 命令を使用します。
この命令の特徴は、「メモリの値が0でなければ、ループ開始。0ならば対応するループ終了’]’ までスキップする。」というものです。
また、言い換えれば、ループ終了記号まで到達したタイミングでメモリの値が0でなければ再びループされてしまいます。条件分岐を実現するには、何度も繰り返されると困るので、メモリの値をリセットする必要のがあります
また、条件分岐に合致した場合の命令のにもポイントがあります。合致した場合の命令を実行したら、ポインターの場所は元に戻す必要があります
例えば、#1の値が0でなければ#2を5にするという命令だとこのように書きます。

// ,
#1 = Console.ReadKey().KeyChar;
// [
if (#1 != 0) {
    // [-]
    #1 = 0;
    // >+++++<
    #2 = 5;
    # = 1;
// ]
}

様々な条件分岐

if (#1)

#1の値が0でなければ実行する命令のです。

// [
if (#1 != 0) {
    // [-]
    #1 = 0;
    // 命令
// ]
}

Brainf*ckコードとだけだとこの通り

[[-] 命令 ]

このコードはシンプルでいいですね。ただ条件判定するのに使った値は強制リセットされます。もしリセットしたくなかったこんな感じで書きます。
なお、#1の値は#3に移動します。

// [>>+>+<<<-]
#3 = #4 = #1;
#1 = 0;
// >>>
# = 4;
// [
if (#4 != 0) {
    // [-]
    #4 = 0;
    // 命令
// ]
}

一気に長くなる上に、パフォーマンスも一気に悪化します。リセットしてもいいか行けないかで使い分けることが大事です。

if (!#1)

こんどは#1が0なら実行するコートです。
フラグとして#2を使用します。つまり、値のメモリの他にフラグ管理用のメモリが1つ必要です。

// >+<
#2 = true;
// [
if (#1 != 0) {
    // > -
    #2 = false;
    // < [-]
    #1 = 0;
// ]
}
// > [
if (#2) {
    // -
    #2 = false;
    // 命令
// ]
}

Brainf*ckだけのコードだとこんな感じ

>+<[>-<[-]]>[- 命令 ]

 

if (#1 || #2)

OR論理式の場合は、どちらかがtrueならtrueなので、非常に簡単に組むことができます。
#3をフラグとして使用しています。

// [
if (#1) {
    // [-]
    #1 = 0;
    // >>+<<
    #3 = true;
// ]
}
// > [
if (#2) {
    // [-]
    #2 = 0;
    // >+<
    #3 = true;
// ]
}
// > [
if (#3) {
    // [-]
    #3 = 0;
    // 内容
// ]
}

Brainf*ckコードだけならこんな感じ。

[[-]>>+<<]>[[-]>+<]>[[-] 内容 ]

if (#1 && #2)

AND論理式の方法。フラグを管理してしまうのが一番楽です。
ANDの場合、どちらかがfalseだとfalseを返しますので、それを利用します。
#3, #4をフラグとして使用しています。

// >>+
#3 = true;
// << [
if (#1) {
    // [-]
    #1 = 0;
    // >>> + <<<
    #4 = true;
// ]
}
// >>> [
if (#4) {
    // -
    #4 = false;
    // <-
    #3 = false;
    // >
    # = 4
// ]
// << [
if (#2) {
    // [-]
    #2 = 0;
    // >> + <<
    #4 = true;
// ]
}
// >> [
if (#4) {
    // -
    #4 = false;
    // <[-]
    #3 = 0;
    // >
    # = 4
// ]
}
// <[
if (#3) {
    // -
    #3 = false;
    // 内容
// ]
}

Brainf*ckだけだとこのような感じ。

>>+<<[[-]>>>+<<<]>>>[-<->]<<[[-]>>+<<]>>[-<[-]>]<[-]

なぜANDだけでこんなに大変なのか。そういう仕様なんです。

if (#1 >= #2)

ここから一気に厄介になります。
条件分岐の基礎である合致(==)より先にこちらを紹介する理由は、こちらの方が楽だからです。
まず、条件判断の流れとなのですが、Brainf*ckでは0か、1以上かという判断しかできません。また、先ほどのを見ていただければわかると思いますが、「1以上かなら〜」の方がコードが短くすみます。(無駄なフラグ管理が必要ないため)
なので、計算式は #1 – #2 + 1 >= 1 という風に変形します。これは#1 >= #2と同じですね。
また、Brainf*ckではメモリの値がマイナスになってはいけません。 なので、いちいちマイナスにならないようにチェックする必要があります。
このコードでは#3,#6はフラグ管理用、#4,#5は一時的利用のメモリです。

// >>=
#3 = true;
// <-
#2--;
// < [
while (#1 != 0) {
    // [->>>+>+<<<<]
    #4 = #5 = #1;
    #1 = 0;
    // >>>>>+
    #6 = true;
    // < [
    if (#5) {
        // >- <[-]
        #6 = false; #5 = 0;
        // <->
        #4--;
    // ]
    }
    // > [
    if (#6) {
        // -
        #6 = false;
        // <<< - >>>
        #3 = 0;
    // ]
    }
    // << [-<<< + >>>]
    #1 = #4; #4 = 0;
    // <<<
    # = 1;
//]
}
// >> [
if (#3) {
    // -
    #3 = false;
    // 内容
// ]
}

一気に長くなりました。また、いちいちマイナスにならないようにチェックをしているので、値をコピーしまくっています。
Brainf*ckコードだけだと以下の通り。

>>+<-<[[->>>+>+<<<<]>>>>>+<[>-<[-]<->][-<<<->>>]<<[-<<<+>>>]<<<]>>[- 内容 ]

もうここまで長いとやる気なくす。実行速度も遅いし、どうにかしないといけないですね。

まとめ

条件分岐として一番肝心な#1==#2とかをパート1で紹介していない理由、 わかりましたでしょうか。
ええ、ものすごい長くなるので一度に紹介するとこっちの身がもたないんですよね…
もしもっとパフォーマンスがあげれるとか、いい方法があればコメントで教えてください。

クソ言語は神秘