フィールド全体を通して規則性に乏しく,
どういう経路を選んでもかなりのバイト数を要する問題は, しばしば長文と呼ばれます.
今回は, 数値関数の助けを借りて長文を攻略する手法を紹介しましょう.
不規則な長文であるほど, 多くの種類のパーツを作りたいわけですが,
それら全てについて, 良い圧縮率を誇る関数を考えるのは, 困難といえるでしょう.
例えば1変数関数f(X)を用いて, f(-)の形で多くの種類のパーツを作る場合,
当然その引数もたくさんの種類が必要になってくるはずで,
でも引数の部分が巨大になると良い圧縮率は得られません.
そこで, 数値に注目します. 数値は, 1から255までの255種類ありながら, どれもが1Bとして扱われます.
したがって, 上手い数値関数f(T)を作ると,
255種類のパーツが, 全て2バイトf(-)として記述できる!
ことになります. もちろん「255種類のパーツ」とは言っても,
あまりに無秩序に各数値に割り当てるのは困難です.
そこで, 「ある程度統一性のある255種類」であり, なおかつ「長文圧縮に有効」と考えられるものとして,
次のようなものを数値に対応させることが考えられます:
これらの使い分けについてですが, 第1, 第2の方法は, 左にも右にも曲がりやすいですね.
第2の方法は, 後ろを振り返れなくなっているので問題によっては使えないですが,
使える問題だと, 第1の方法に比べて1つの数値で多く配置できるので有効になるかもしれません.
第3, 第4の方法についてですが, これもやはり第4の方法では, 後ろを振り返れない代わりに圧縮率を追求しています.
第1の方法と第3の方法, 第2の方法と第4の方法を比べた場合, どういう形が作れるかという点では完全に一致しますが,
圧縮率は経路によって異なってきそうです. 例えば
ssssssss
だと第1の方法では2つ, 第3の方法では1つの数値に対応しますし,
lsrslsrslsrslsrs
だと第1の方法では2つ, 第3の方法では (lをrrrと見なして) 3つの数値に対応します.
基本的には, 直進が多いか否かで選び分けると良さそうですね.
すべて, f(1)からf(255)にそれっぽいのが現れるようになっています.
a(T,A):ra(T-A,A) f(T):a(T,64)sa(T,16)sa(T,4)sa(T,1)s原理は詳しく説明しませんが, 大体4進法展開して(各桁+1)の回数だけrを並べる感じ.
a(X,T,A):Xa(r,T-A,A) f(T):a(,T,64)sa(,T,16)sa(,T,4)sa(,T,1)s上と大体同じ. 関数定義に2B余分に費やした代わりに, 犠牲が「rsrsrsrs」というあまり要らなさそうなもので済んでいます.
a(T,A):ra(T-A,A) f(T):a(T,64)sa(T,16)sa(T,4)sa(T,1)ls大体同じ. 第4項だけlを追加していて, ssssの代わりにssslsが犠牲になります.
a(T,A):ra(T-A,A) f(T):a(T,64)sa(T,16)lsa(T,4)sa(T,1)s同様.
a(T,X):X b(N,T):rb(N,T-63)a(64-T,sb(N-1,T+T+T+T)) f(T):b(4,T)T/63という分数の4進法小数展開を4段階で打ち切ったもの.
a(T,X):X b(A,B,X):b(A-127,B,s)a(128-A,Xb(A+A,B-1,r)) f(T):b(T,8,r)これも上記と同様で, T/127という有理数の2進法小数展開を打ち切っている,,はずです.
さて, f(T)という関数で, 色んなパーツを255種類すべて作れるようになりました!
例えばs,rs,rrs,rrrsを4つ並べた場合には, 「4歩が2Bになる!」というわけです.
しかし実際にはもうひと工夫することで, よりよい圧縮率が実現できます.
例えば次の定義をしましょう:
g(A,B,C,D,E,F):f(A)f(B)f(C)f(D)f(E)f(F)
この定義により, 6つの数値を7Bで呼び出せるようになります.
「4歩が1つの数値になる!」だったので, 「24歩が7Bになる!」となり, 約3.5歩を1Bにできました!
引数を増やしていくと, 当然どんどん圧縮率は上がります.
仮想的に, 26個を超えて無限に多く引数を持たせると, 圧縮率は実質「4歩が1B」となりますね.
しかし, 高い圧縮率を実現しようとすると, 関数定義の負担も増えていくので, ある程度のところで妥協しないといけません.
例えば, 90個の数値を並べることを考えましょう. この場合, gの引数を変化させると,
(gの定義) + (実行部分)
は次のように変化します:
gの定義は簡単ですね. 1つ引数を追加するたびに3B増えます.
実行部分は, 90の約数のところは簡単ですね. (90/N) * (N+1)というような式です.
引数7個の場合は約数になってはいないのですが, 91個の数値を並べてしまうことにしてしまうと, 104Bになります.
引数4個の場合はこの考え方だと92個の数値を並べて115Bになりそうですが,
(88個の数値を並べる) + f(A) + f(B)
とした方が,
(88個の数値を並べる) + g(A,B,C,D)
よりも短くなっています. このような計算を経て, 今回は引数が5個 or 6個として圧縮するのが最善だと分かります.
結局この方法で長文を解くには,
というステップが必要です. 問題によっては, s, rs, rrs, rrrsが500個以上も並ぶ場合もあり,
数値の並びに変換するステップは特に大変なので, 何らかのプログラムを用意しておくといいでしょう.
もちろん, 有用な「並べ方」はここに挙げたものばかりとは限りませんし,
同じ並べ方についても, より良い関数定義がありうるかもしれません.
是非色んな手法を模索してみてください!