Brainfuckの文章出力を短く書こう
皆様ご存知、+-<>[],.の8文字だけでプログラムを記述するBrainfuckという言語があります。Brainfuck及び基本的な構文については以下の記事が大変わかりやすいので、ぜひご参考にしてください。
さて、この世にはBrainfuckで「Atcoderの問題を解く勢力」と、「文章出力をいかに短く書くかを競う勢力」の2大勢力が存在しており、今回は後者の 「文章入力をいかに短く書くかを競う勢力」に参入する皆様のためのチュートリアル的な記事 となります。
なお、私はこちらのインタープリタを使用しています。今回のプログラムには複雑なものもありますが、このインタープリタの「ステップ実行」をすることで理解につながるかと。また、今回はこちらのASCIIコード変換機がかなり重宝します。
今回の動機(余談)
今回の記事を書こうと考えた理由は2025年のお正月に際して「Happy new year!」をいかに短く書くかを競い合ったためです。詳しくは、こちらのXの返信ツリーへ。なお、記事執筆時点での私の記録は、以下の103文字でした。より短くかけた方は是非教えてください。
++++++++
[>
>--
>+
>+++
>++++
>-------
[+++++++++++<]
<-]
>>.
>+.
>..
>+.
>.
<<--.
<++++.
>>--.
>.
<++.
<<.
----.>
++++.
>>+.
初期化部分と調整・出力部分
Brainfuckで文章を出力するプログラムの多くは、メモリの値をおおよそASCIIの文字部分にする「初期化部分」とそのメモリの値を出力したい文字に調整して出力する「調整・出力部分」に分かれています。
なお、これらの用語は今回便宜上使用する造語です。
例えば、私の名前である「ralutet」を出力するプログラムを以下のように分解できます。
# 初期化部分
# 0 0 114 96 108となる
++++++
[>
>++
>-
>+
[+++++++++++++++++<]
<-]
#調整・出力部分
>>. # r 114
>+. # a 97
>. # l 108
<<+++. # u 117
-. # t 116
>++++. # e 101
<. # t 116
初期化部分
前述の通り、まずはメモリの値をだいたいASCIIの文字部分にします。ASCIIでは、32が空白、65~90がA~Z、97~112がa~zに割り当てられているため、ここではループ等を駆使して、そのあたりの値にします。
初期値とその配置を決める
後述するループ等を使用して初期化する前に、まずはどの値に初期化するかを考える必要があります。主に以下の点を考慮して決定していますが、使うループによって考えることは変わってきます。
- 概ね出力する文字のASCIIコードとなっていること
- 特に、一番最初に出力する文字となっているとよい
- 全てある値の倍数となっていること
- 理論上はASCIIコードの平方根となる7~12が望ましい...
- グループ間の移動距離が少ないようにする
例えば、「ralutet」のASCIIコードを縦軸にプロットすると以下のようになり、概ね97~101(a~e)と108(l)と114~117(r~u)にグループ分けできます。そして、グループ間内の一番最初に出力する文字のASCIIコードの差は6と11で、さらに96,108,114が全て6の倍数なので、6の倍数で初期化しましょう。また、グループ間の移動回数を考えると、97~101(a~e)のグループと114~117(r~u)のグループが隣接して配置していると都合がよいです。
ということで、114 96 108とメモリの値を初期化することに決定します。

ループで初期化する
次にループを使ってメモリの値を初期化するプログラムを書きます。その時に使える技が何個かありますので、ひとつづつご紹介いたします。
基本のループ
brainfuckで一番基本となるループです。しかし、+や<が何度も表記されている感じ、さらに工夫の余地がありそうです。
# 0 0 81 90 81 63
+++++++++
[
>+++++++++
>++++++++++
>+++++++++
>+++++++
<<<<-]
帰るときに全体に足すループ
先ほどの同じ文字が何度も表記される問題を解決したループです。右に行くときに細かい値を足し引きして、左に帰るときにまとめて大きな値を足していきます。汎用性が高いのでおすすめです。先日あじさんのツールで知りました。
# 0 0 81 90 81 63
+++++++++
[>
>+
>++
>+
>-
[++++++++<]
<-]
アンダーフローでのループ
brainfuckの各メモリは0~255で、基本的に0の状態から1を引くと255になります。64付近などの特定の場合に限りますが、これを利用することでかなり短く書くことができます。また、オーバーフローとアンダーフローは意外と様々なところで登場するので、頭に入れておくといいです。
# 0 63
----
[
>+
<----]
少しずつ増やすループ
二重のループを使って、そこそこ短いプログラムで少しずつ違う値を作成できます。ただし、メモリの値の順番は固定であり、大文字/小文字が混在している場合はあまり使えません。空白(32)くらいは後の方法で対応することが可能です。これはredditで知りました。
# 0 0 120 116 112 108 以下省略
++++++
[
>+++++
<-]
↑ここまでで 0 30 を作る
>
[>
[++++>]
++++
[<]
>-]
出力に空白(32)や大文字(65~90)を含めたい場合、メインのループの横でその30を作ることができます。
# 0 0 30 76 120 116 112 108 以下省略
++++++
[
>+++++
<-]
↑ここまでで 0 30 を作る
>
[>
+> #ここで30を作る
------> #ここで76を作る
[++++>]
++++
[<]
>-]
最初の30を作る部分をオーバーフロー/アンダーフローを使ってどうにかかけないかと思いましたが、難しそうですね。
調整・出力部分
先ほど大体作成したメモリの値を微調整して、目的となる文字を出力します。これは特に工夫しなくても短く書けますが、ループを使って短くかける場合もあります。書くことがあまりないですね。
変則技
基本的には初期化部分と調整・出力部分に分かれると言いましたが、それ以外に分類されるものも一応存在します。
頭の大文字を崩す
まず大きなループで大文字を作成してから、その値を使用して、その後使う小文字の初期化を行う方法です。
----
[
>+
<----]
# ↑ここまでで63を作る
>++. # A 65
# ↑ここまででA(65)を出力する
[>
[++++++>]
-
[<]
>-]
# ↑少しずつ増やすループで 0 0 127 121 115 以下省略 を作る
>>>---.. # pp 112
>-. # l 108
>--. # e 101
さいごに
もし面白い短い記法があれば、教えていただけると幸いです。
皆様もよきbrainfuckでの「文章入力をいかに短く書くかを競う勢力」としての生活をお楽しみください。ここまでお読みいただきありがとうございました。
Discussion