🏌️

Brainf**k ゴルフ短縮事例解説-FizzBuzz(212)

2024/11/30に公開

はじめに

※本記事は、2022/12/10にQiitaに投稿した記事を移行したものです
この記事は、多言語FizzBuzz Advent Calendar 2022の10日目の記事ということで、esolangでのFizzBuzzネタの一環として、今回は Brainf**k を取り上げているものです。

背景

以前にもBrainf**k版FizzBuzzには取り組んでおり、3年前のBrainf**k ゴルフ事例解説-FizzBuzz(214)の時点で214文字を達成していました。
今回は、そのコードをベースに2文字短縮したということで、改めて記事にすることにしました。

なお、解説はその差分についてとなりますので、コード全体の詳細については、上述の記事をご覧になることをお勧めします。

コード解説

コード概要

更新後のコード全体は以下の通りです。

一応、概要としては、以下のようなセルの役割をおさえていただければ良いと思います。

  • Buzz出力用の5の倍数判定は、全体の 10×2×5 のループのカウンタg,b,f の最内の f(5周期) で兼ねる。
  • Fizz出力用の3の倍数判定は t のセルを3周期のカウンタとして使うことで行う。
  • Fizz, Buzz 出力に使う文字コードとして、
    • F のセルは F, B で兼用する。( B の場合は4減らして出力後戻す )
    • h のセルは 1 減らして i にして使う。 ( 出力後戻す )
    • z のセルは u, z で兼用する。( u の場合は5減らして出力後戻す )
  • 数値出力用のセルは10の位から順に X,Y で、毎周期 X の加算と、最外ループの切り替わりでの X のリセットと Y 加算を行う。
  • d は数値2桁出力用のフラグ、n は改行出力用の文字コード10保持用に使う。

この中で今回更新したのは、3周期のカウンタ t の扱いと、Fizz/Buzz 出力用の F,h,z の位置関係の2か所です。

fizzbuzz.bf
 1: ** FizzBuzz(212) **
 2: ** memory layout: d_YXgbft__nFhz **
 3:
 4: ** initialize **
 5: f++++[-
 6:  f>>>>n[->F+>h++>z+++<<<n]
 7:  n+>F+>h++>z-[+++++++++<]
 8:  <<t,-
 9:  t<<<g,<X+Y<+[+++++++++++>]
10: >f]
11:
12: ** main loop **
13: f<<g[
14:  g>b++[
15:   b>f+++++[-
16:    f<<<<Y+            ** increment **
17:
18:    ** begin core part **
19:    Y>>>>>t[
20:     t+++<f[
21:      f[<]<d[>]>>XY?[.<]  ** print number **
22:     ]
23:    ]
24:
25:    ?>>[>]?>[n
26:     n>F.>h+.->z..   ** print Fizz **
27:    z[<]]
28:
29:    <<t---<f[>]?>[t
30:     t>>>>F+[-----.++++>>]<<z+..  ** print Buzz **
31:    z[<]]
32:    ** end core part **
33:
34:    >n.                ** newline **
35:   n<<<t+<f]
36:  <-b]
37:  ** carry ( move up ) operation **
38:  <<X+Y<----------<<d,
39: >>>>g-]

更新点1: t の扱い

では、更新点の1つ目として、t の扱いについてです。

以前のコードとの、t 初期化箇所の差分が次のようになっています。

t初期化箇所差分
  4: ** initialize **
  5: f++++[-
(略)
- 8:  <<t,--
+ 8:  <<t,-
(略)
 10: >f]

これは、F,h,z 等をループ処理で加算していくループの中で、t は ,(EOFにより-1セット) を絡めて一定の値として従来 -3 をセットするようにしていたのですが、これを -2 に替えて 1文字短縮したものです。
従来は、ループの最初に t を加算して、-2 → -1 → 0 ( Fizz出力後 -3 にリセット ) という遷移を行っていたのですが、t の加算をループの最後にずらして -2 → -1 → 0 ( Fizz出力後 -2 にリセット ) という遷移にすることで、初期化箇所の1文字短縮の効果が得られると気付いたためです。

次のように、ループ毎に t の加算を行う位置をずらしただけですので、この t の操作でのコード長への影響はありません。

t操作部分差分
 18:    ** begin core part **
-19:    Y>>>>>t+[
+19:    Y>>>>>t[
 20:     t+++<f[
(略)
 34:    >n.                ** newline **
-35:   n<<<<f]
+35:   n<<<t+<f]
 36:  <-b]

更新点2: F,h,zの位置の逆転

そして2つめの更新点ですが、こちらは F,h,z のセルの位置を逆転したことになります。
他のセルも併せ、冒頭のレイアウト部分の差分を次に載せます。

セル位置差分
- 2: ** memory layout: d_YXgbft__nzhF **
+ 2: ** memory layout: d_YXgbft__nFhz **

この変更による、初期化部分のコード長に差は特にないため、この部分の差分は割愛します。

影響があるのはFizz出力とBuzz出力ですが、前者の方で1文字の短縮効果が得られています。
次がその差分です。

Fizz出力部差分
-26:     n>>>F.<h+.-<z..   ** print Fizz **
-27:    z<<]
+26:     n>F.>h+.->z..   ** print Fizz **
+27:    z[<]]

以前の方が、出力の開始となる F が遠い場所にあるため、n→Fに3移動、そこから h,z と戻って n の1つ左まで来て ] に到着ということで合計 >×3、<×4 の7文字の移動分のコードを要しました。
しかし、今回 n と F が隣接していることで、n→F→h→z と >×3 は変わらないものの、戻りを [<] の3文字の一括移動に替えることができ、これで1文字短縮です。

実は以前もこちらの方が短いことには気付いていたのですが、Buzz 出力で行っていた F から -4 での B 出力、z から -5 での u 出力で、-4 をオーバーラップしている部分の代替案が見つからず見送っていたのでした。
しかし、次の差分のような形で何とかできたので採用に至ったということになります。

Buzz出力部差分
-30:     t>>>>z->>F[----.++++<<]>>z+..  ** print Buzz **
-31:    z<<]
+30:     t>>>>F+[-----.++++>>]<<z+..  ** print Buzz **
+31:    z[<]]

どのような対処かというと、F に予め +1 しておいて、それから -5 でオーバーラップするというものです。もともと [----.++++<<] として -4,+4 を F,z 共に適用していたところ、[-----.++++>>] で -5,+4 にして対応しています。( [] 内で増減がバランスしてないところはその外側でカバーします )
こちらも、移動分としては 1文字短縮効果があるのですが、最初の F +1 が1文字損になっていて、ここではトータルコード長の増減は無しです。

おわりに

ということで、地味ながらネタ数を稼ぐためにたまにはこのような細かいところの更新の解説も、ということで紹介しました。
…この間にトップのmitchsさんは205Bまで短縮して差は広がるばかりなんですが、まあ上には上がいるものですね、ということで。

Discussion