🧠

Brainf**k講座(5) 条件分岐の多段化

2024/12/01に公開

はじめに

※本記事は、2016/7/12にHatenaBlogに投稿した記事を移行したものです
前回から条件分岐に入りましたBrainf**k講座、条件分岐にも色々なバリエーションがありますので、今回・次回と分けて説明していきたいと思います。

講座一覧

  1. Hello, BF!
    導入およびシンプルなループによる数値生成
  2. 数値入力と簡単なループ
    , による入力と数値解釈、簡単なループの実例
  3. スライド型ループ
    ループ継続の判断を行うセルの位置をずらしていくループ
  4. 条件分岐
    if文、if-else文相当の処理
  5. 条件分岐の多段化 (今回)
    switch文相当の処理
  6. 非破壊的条件分岐
    条件変数を壊さない条件分岐
  7. divmodアルゴリズムと数値出力
    除算・剰余算のアルゴリズムと、それを利用した不定長の数値出力
  8. BCD演算
    10進数による任意長の数値の計算

※上記以外も含め、各記事はBrainf**kの記事一覧から辿ることができます

実行環境

  • ideone
    オンラインのIDE環境で、bffという処理系が使えます。この講座は基本的にこのbffに対応したものです。
  • Brainf**kインタープリタ
    あじさんという方が作られたブラウザ上で動くインタプリタです。細かい挙動の調整や、途中のセルの状態のダンプができたり、重宝しています。
    ※オプションで「バッファの型」を「4バイト符号付き整数」、「標準入力の終端」を「-1」と設定すると、bffと同じ挙動になります。

今回の話題

条件分岐の多段化

前回はif文とif-else文でした。が、実際にコードを組む場合は、1つの値を評価して、その値毎に処理を変える、いわゆる switch文に相当する処理が必要になることもあります。

ここで、前回のif-elseを入れ子にすることで実装することもできるのですが、そのままだと各ケースの分 else用の条件変数が増えることになり、コードが複雑になってしまいます。

実は、このような場合、else用の条件変数を共通化して多段の条件分岐にする方法があります。次から実例を挙げてみていきたいと思います。

多段化の実例1:one-two-many-counter

とある人々は、数を「いち、に、たくさん」と数えるそうですが、それを英語で実装してみましょう。次のような問題です。

  • 1桁の数字1~9が入力されるとき、1ならば“one”を、2ならば“two”を、3以上ならば“many”を出力する。

Rubyの場合は case文があるので、それで書くこともできるのですが、多段のif-elseで次のようにも書けます。なお、最初に 1 を引いて、条件分岐の境界を 0,1 にしています。

i=gets.to_i-1
if i>0
  if i>1
    puts "many"
  else
    puts "two"
  end
else
  puts "one"
end

これをBrainf**kで書くと次のようになります。

** make alphabet code **
C+++++++++[->>c+>l++>u+++[++++++++++<]<]
** convert **
C,>E+++++++[-<C------->E]
** switch **
E+<
C[
 C-[[-]C>E-
  E>>l+.<c--.>l+.>u++++.<<<E
 E<C]
 >E[
  E->>>u-.+++.<l+++.<<E
 E]
E<C]
>E[
  E->>l+++.-.<c++.<E
E]

始めの方は、出力用文字コードの生成と、入力された数字のコンバート ( 文字コード-49で“1”→0,“2”→1, …へ ) で、“switch”以降がswitch文に相当します。

この部分を抜粋すると、次のような形をしています。

 1: E+<
 2: C[
 3:  C-[[-]C>E
 4:   E- **“many”出力** E
 5:  E<C]
 6:  >E[
 7:   E- **“two”出力** E
 8:  E]
 9: E<C]
10: >E[
11:   E- **“one”出力** E
12: E]

まず、条件変数であるCは、1ずつ減らしながら評価していきます。各Cの値によって次のように挙動が変わります。

  • Cが0の場合
    • 2行目の[の中に入らず、10行目に飛ぶ
    • 10行目ではEがクリアされていないので[の中に入り11行目の処理を行う
    • 11行目でEがクリアされるので、12行目で]から出る
  • Cが1の場合
    • 2行目の[の中に入る
    • 3行目でCが1減らされ、0になる
    • そのため、3行目の[の中に入らず、6行目に飛ぶ
    • 6行目ではEがクリアされていないので[の中に入り7行目の処理を行う
    • E,Cがクリアされているので、8,9行目の]から出、10行目の[には入らない
  • Cが2以上の場合
    • 2行目の[の中に入る
    • 3行目でCが1減らされるが、依然1以上である
    • そのため、3行目の[の中に入り、そこでCがクリアされる
    • 4行目でEもクリアされる
    • E,Cがクリアされているので、5行目の]から出、6行目の[に入らず、9行目の]から出、10行目の[に入らない

このようにして、else用制御変数 E を共通化して、if-elseを入れ子にすることができます。2行目~12行目 ( switch文全体 ) も、3行目~8行目も、C[ C,Eクリア C]>E[ Eクリア E]の形になっているため、各処理は似たような形になります。
なお、コード上の記述としては、defaultの分が最初に来ることに注意してください。

多段化の実例2: 音階変換

ではもう一例見てみましょう。今度は次のような音階変換の問題です。

  • A~Gからなる文字列が入力されるので、それぞれを音階と見て、イタリア語 ( A: La, B: Si, C: Do, D: Re, E: Mi, F: Fa, G: Sol ) に変換し出力する。

文字の入力ループがあり、ケースが先ほどの例の3ケースから7ケースに増えていますが、switch部分の構成としてはほとんど同じです。Brainf**kのコードは次のようになります。( ideoneでの実行例はこちら )

音階変換
** create character codes for FMTbip
X+++++++[->>F--->M-->T->b+>i++>p+++[+++++++++++++<]<]
** loop **
X,+[
 ** convert 'A' to 0 'B' to 1 respectively ** 
 >Y++++++[-<X----------->Y]
 ** switch **
 Y+<X
 X[-[-[-[-[-[->Y
       * case G (Sol) *
       Y->>>T-.+>>>p-.---.++++<<<<<<Y
      <X]>Y[
       * case F (Fa) *
       Y->F.>>>b-.+<<<<Y
      Y]
     <X]>Y[
       * case E (Mi) *
       Y->>M.>>>i.<<<<<Y
     Y]
    <X]>Y[
       * case D (Re) *
       Y->>>T--.++>b+++.---<<<<Y
    Y]
   <X]>Y[
       * case C (Do) *
       Y->F--.++>>>>>p-.+<<<<<<Y
   Y]
  <X]>Y[
       * case B (Si) *
       Y->>>T-.+>>i.<<<<<Y
  Y]
 <X]>Y[
       * case A (La) *
       Y->>M-.+>>b-.+<<<<Y
 Y]
<X,+]

条件変数はX、else用条件変数はYとしています。先ほどと若干書き方が違うように見えますが、同じパターンのコードが現れることは分かっているのでまとめてしまっています。

まとめ

  • switch文相当の処理は、else用条件変数を共通化した if-else文の入れ子で実装できる

終わりに

今回は図で描きにくいので文章のみとなりましたがいかがでしょうか。ただ、パターンを掴んでしまえば、同じ形の繰り返しで書けるため、割と分かり易いのではないかと思います。次回は条件分岐の最後の話題になります。

Discussion