ppencode2

Shibuya.pm で ppencode 2 についての話をしてきました。

http://shinh.skr.jp/slide/ppencode2/000.html

つうてもしかしはてなんの話したらいいんだっけ感があって、なんの話をしたんだっけ。。

しっかしまあ、できたんだなーよかったねー感はありますね。

おまけというかなふたつ

http://shinh.skr.jp/obf/yuine_kw.pl

http://shinh.skr.jp/obf/yuine_sym.pl

はまあなんかそれなりに面白い気がしないでもないが…

これはまあ通常、通常てのが何かは知らんが通常、よりは少し大変で。

なんだか quote に足りない文字があったりすりがたいしたことでもなく

まとめるとねむくてだるい

Python 2/3 で小文字アルファベットと丸括弧だけで Quine を書く

http://shinh.hatenablog.com/entries/2015/05/11

の続き。 @mametter が next(reversed(range(i))) で i-1 ができるということと、 @phoenixstarhiro が unichr(x)in(list(unichr(y))) で x==y ができるということを発見してくれたため、現実的なサイズで Quine が書けました。

https://raw.githubusercontent.com/shinh/hack/master/alparen_py/alparen_quine.py

https://raw.githubusercontent.com/shinh/hack/master/alparen_py/alparen_quine.py3

今までいろいろクソコードを書いてきて、 http://shinh.skr.jp/obf/ にある程度まとめてたりしたんですが、最近全然アップデートしてないので、 github に新しい置き場を作ってみました。

https://github.com/shinh/hack

思いついた時に適当に色々足していこうかと思います。

Python 2/3 で小文字アルファベットと丸括弧だけでプログラムを書く

http://d.hatena.ne.jp/nishiohirokazu/20120906/1346938523

を読んで、なるほどー、でもカンマが必要というのは説得力が弱いな、と思ったので頑張ってみました。任意の Python コードを小文字アルファベットと丸括弧だけに変換できます。

http://shinh.skr.jp/obf/ppencode_py.html

根っこの部分

は西尾さんのコードと同じ、つまり

print(bytearray((XXX)for(x)in(YYY)))

です。これ正直私の Python 力では自力では出てこないと思いますね。。

西尾さんは YYY の部分が大雑把に ASCII コードに対応する物体の tuple になってます。ここに tuple が必要だというのが、西尾さんの「カンマを使わずにイテレート可能なオブジェクトを作ろうとすると…」の元だと思うのですが、まぁ range/xrange はカンマ使わないわけです。

任意の数字を作る

さて、 range 使うとなると文字列長をあらわす数字が必要です。特に 256 までは任意の文字を表現するために必須と言えます。

西尾さんはタプルの長さを使ったりして、適切なサイズの文字列を作って len してました。カンマが無いと、単項演算しか無いわけで、だいぶやれることが減るのですが、いろいろ考えてると、数値を引数として数値をいじってかえす関数をいくつか作ることができます。例えば

len(repr(zip(bytearray(i))))

は i に入ってる数値を 6 倍する関数です。こういう感じで、 3 倍するであるとか、 4 倍して 14 足すであるとか、微妙な材料が色々得られます。 2 倍とか +X とか便利なものはありません。要は repr で文字列を伸ばしていて、その前にした演算によって伸びる率が違う、って感じです。あとは数値を減らす手段としては bin, oct, str, hex を使って、 len(oct(i)) などとするのが唯一だと思います。これらは底を 2, 8, 10, 16 とした log_k(i)+1 として機能します。

まあそんな連中でも 1000 程度までの数字なら作れてたみたいです。残念ながら 256 文字も使えば既にすごく遅いので、 JavaScript でのエンコーダでは 255 文字までしかサポートされてません。できあがったテーブルは

http://shinh.skr.jp/obf/alpha_paren_py2_data.js

にあります。

これで、大元の for の右っかわができそうな気配です

print(bytearray((XXX)for(i)in(range(YYY))))

YYY にこの方法で生成した数字をあらわす式を入れれば文字列長だけのループが回せるのです。

テーブルをひきたい

さて XXX の部分です。連番の数字を入力として、テーブル引きをしないといけないです。普通のテーブル引きは [] とか , を使う必要があると思うので、ダメです。唯一考えられたのはクソ遅い条件分岐の連打です。こういうやつ

if i == 0:
  return XXX0
if i <= 1:
  return XXX1
if i <= 2:
  return XXX2
...

こういう形なら、 Python の3項演算子で表現できます。 i == 0 はすごく簡単です…

i <= 1 をしたい

が、 i <= 1 から先は簡単でないです。数値 i を受け取って、 i が k 以下なら 0 、そうでなければ非ゼロを返す関数を作りたいわけですが…難しいです。手持ちの材料にあるのは定数かけ算と log(i)+1 です。そしてどっちも絶対に 0 を返しません。

なんかゼロになるもの…なるもの…と考えたところ、 48 がありました。 48 は '0' の ASCII code なので、 int(chr(48)) は 0 になるのです。というわけで、 1 を受け取ると 48 になって、 2 を受け取ると 49 から 57 の数字になる(Python の int 関数は数値じゃないもの受けると例外投げます)関数を探せば良いです。ありました。

len(repr(zip(bytearray(len(bin(len(repr(list(bytearray(len(repr(zip(str(bytearray(i)))))))))))))))

長いです。

比較をしたい

i <= 1 的なものができました。文字列長256までをサポートするとして、 256 個の i <= k 的な条件が並ぶので、残りの 254 個の k をどうにかしないといけないです。これは i が k 以下なら 1 、 k 以上なら 2 になるような関数をプログラムで探索すれば良いです。

0 を作るのと違って、これは割と現実的な話です。ひたすらかけ算を繰り返していくと、どこかでケタ上がりするわけで、 k 以下ならまだケタ上がりしてないけど、 k 以上ならケタ上がりしている、みたいな関数を探索すれば良いのです。 bin, oct, str, hex の 4 種類が使えるので、 2, 8, 10, 16 進数のどれかでケタ上がりしてれば OK です。

例えば、 i <= 3 は、 3 倍する材料は発見されているので、 len(str(i*3)) で良いです。 i == 3 の時は i * 3 = 9 はまだ 10 進数で一桁で、 i == 4 なら二桁になってるわけです。

この探索は、適当に総当たりをしたので、かなり時間がかかりました。

1, 2 だけにしたい

上記みたいな方法で作ると、 k より大幅におおきな数字に対して、 3 よりおおきな数字を返すことがあります。例えばさっきの len(str(i*3)) は i == 34 あたりから 3 を返しはじめちゃいます。

これの対処は簡単で、 len(str(i*6)) を繰り返せば良いです。 1 を 6 倍すると 6 なので 1 ケタですが、それよりおおきな数字はいずれ 2 に収束します。というかこれは log ですごく高速に収束するので、小さいプログラムの範囲では 1 回以上必要ないです。

これでできた比較用のテーブルは

http://shinh.skr.jp/obf/alpha_paren_py2_data.js

を CMPS2 で検索すると出てきます。

おまけ

なんのこっちゃですがだいたいこれ組み合わせるとできます。

str(bytearray()) の結果が全然違うため、 Python2 と Python3 両方で動くコードを作るのはどうも至難の技ぽかったので、しょうがないので別々にジェネレータを作りました。

あと、なんか Python3 のバグ踏んだ気がするので回避しておきました。暇な時に本当にバグか見ておこうと思っています。

重複なしで Hello, world! を Ruby だけで

CodeIQ に Hello, world! を3つ以上の言語で書きなさい、ただしそれぞれで使われてる文字種に重複があったらダメ、という問題がありました。

http://nabetani.hatenablog.com/entry/codeiq_hwx3_q766

私の解答は6言語使ったものでした。

http://shinh.hatenablog.com/entries/2014/03/18

今回は Ruby だけを使って、4つの Hello, world! ができました。言語が一つしか無いと複数言語を使うより色々なことをする余地が減るので、当然大変なので6つとかはちょっと無理です。

5つも厳しいかなぁと思っていて、なんらかの文字を出力する方法が4つ以上思いあたらないからです。4つというのは

  • puts/putc/print 系
  • IO#<<
  • eval
  • ラムダを使った eval

の4つです。あとは単純に任意文字列を作る手段が足りなくなっていきます。順に見ていきます

ひとつめ puts+putc 使用文字種10種類: " +89;cpstu"

putc 9+9+9+9+9+9+9+9;putc 8+8+8+8+8+8+8+9+9+9+9+9;putc 9+99;putc 9+99;putc 8+8+8+8+8+8+9+9+9+9+9+9+9;putc 8+9+9+9+9;putc 8+8+8+8;putc 8+8+8+8+8+8+8+9+9+9+9+9+9+9;putc 8+8+8+8+8+8+9+9+9+9+9+9+9;putc 8+8+98;putc 9+99;putc 8+8+8+8+8+8+8+8+9+9+9+9;putc 8+8+8+9;puts

色々試しましたが、最終的に丸括弧を消費すると最後で詰むので、丸括弧を使わない、つまり .chr などを必要としない putc がありがたいと判断しました。 8 と 9 だけを使っているのは、後で8進数を使うのでこいつらだけは余るのです。 10 は 8 と 9 では作れませんが、そこは puts で。

ふたつめ IO#<< 使用文字種12種類: "\n$/<=DOSTUz~"

STDOUT<<z=<<SS<<z=/$/=~<<ST
SS
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
ST
STDOUT<<z=<<SS<<z=/$/=~<<ST
SS
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
ST
STDOUT<<z=<<SS<<z=/$/=~<<ST
SS
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
ST
STDOUT<<z=<<SS<<z=/$/=~<<ST
SS
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
ST
...

このコードは長いので以降省略。やってることは単純に、長い文字列作って、その長さを /$/=~ で調べて、それが文字コードになってるから文字列に << してその後 STDOUT に << する、と。

二つの << の優先順位が同じなので、なんとかするために = を使ってます。まぁ正規表現の =~ にも使ってるから丁度良い。文字列は < なのですが、 > が必要なので STDOUT を。まあ大文字はどうせ余りまくります。 $ も重要だと思ってて、このコードで使う必要は別に無いんですけど、最終的にいらなかったのでそのまんま。

みっつめ eval 使用文字種16種類: "\t%01234567Q\\aelv"

eval	%Q%\160\165\164\163\42\110\145\154\154\157\54\40\167\157\162\154\144\41\12\42%

なんというか、そのまんまです。数字をケチってきたのでラクできます。

よっつめ lambda 使用文字種26種類: "!\"#&()*,-.:>?VW[]^_doqrw{}"

->(__){->(&_){_["",:"#{__[?q.ord^?>.ord]}r#{__[?d.ord^?,.ord]}#{__[?d.ord^?).ord]}#{__[?r.ord^?!.ord]}#{__[?d.ord^?!.ord]}","#{__[?q.ord^?V.ord]}#{__[??.ord^?{.ord]}#{__[?q.ord^?:.ord]}#{__[?q.ord^?:.ord]}o,#{__[?_.ord^?[.ord]}#{__[?d.ord^?&.ord]}#{__[?{.ord^?-.ord]}or#{__[?q.ord^?:.ord]}d!#{__[?_.ord^?[.ord]}#{__[?d.ord^?&.ord]}",?w.ord^?W.ord,?w.ord^?}.ord]}[&:"#{__[?q.ord^?#.ord]}#{__[??.ord^?{.ord]}#{__[?d.ord^?).ord]}d"]}[[*?!..?}]]

知らないと意味不明ですが、 Ruby で記号だけで Turing complete にするための、 ->(&_){_["",:"eval","code..."]}[&:"send"] というイディオムを使ってます。ここに ->()[]{}#: などを残すためにこれまでで苦労してました。

追記。 () 残す必要は無かったでした。

->__{->&_{_["",:"#{__[?q.ord^?>.ord]}r#{__[?d.ord^?,.ord]}#{__[?d.ord^?).ord]}#{__[?r.ord^?!.ord]}#{__[?d.ord^?!.ord]}","#{__[?q.ord^?V.ord]}#{__[??.ord^?{.ord]}#{__[?q.ord^?:.ord]}#{__[?q.ord^?:.ord]}o,#{__[?_.ord^?[.ord]}#{__[?d.ord^?&.ord]}#{__[?{.ord^?-.ord]}or#{__[?q.ord^?:.ord]}d!#{__[?_.ord^?[.ord]}#{__[?d.ord^?&.ord]}",?w.ord^?W.ord,?w.ord^?}.ord]}[&:"#{__[?q.ord^?#.ord]}#{__[??.ord^?{.ord]}#{__[?d.ord^?).ord]}d"]}[[*?!..?}]]

感想とか

どこに何を使うか、割と苦労したんですが、トリッキーなコードを書くとどうしても文字種消費しがちなため、完成してしまうと割とシンプルな4つになりました。難易度的にも言語バラバラ6種の方がやや大変でした。

5つ目は文字を出力する手段さえあれば、かなりキツいでしょうがなんとかなるかもしれません。ずるいですが `` を活用するとかも考えましたが、 1>&2 を使わずに出力できる気がせず。

Perl で同じことをすると、拡張正規表現での eval が使える少し古い Perl でも 3 種類が限度なんじゃないかと思っています。 id:ku-ma-me さんと PerlRuby がどっちが強いかみたいな話をしていたのですが、この問題なんかは割と象徴的な気がします。

Ruby はまんべんなく強くて、引き出しが多くて優秀な武器がそこらじゅうにある感じ。 Perl は火縄銃と核兵器

ppencode 2 - 任意の Perl コードを予約語だけの Perl コードに変換する

@TAKESAKO さんが ppencode を作ってから 10 年経って、私が任意の Perl コードを小文字だけに変換するスクリプトを書いてからでも5年経つらしいですが、なんか任意の Perl コードを予約語だけの Perl コードに変換するスクリプトができました。

http://shinh.skr.jp/obf/ppencode.html

オリジナルの ppencode に敬意を表していろいろ雰囲気を似せておきました。割と色んな予約語が使われるようにしてみたりとか。

これは Perl予約語だけあれば Turing complete ということなので、副産物として Quine もできました。

http://golf.shinh.org/reveal.rb?Quine/shinh+%28keywords%29_1431106882&pl

なんというか、 ppencode があれば後は print を eval に変えるだけちゃうん?という当然の疑問があると思うのですが、 ppencode より割と大変だったりします。 ppencode は要は

print chr 72 xor print chr 101 xor print chr 121  # Hey

みたいな感じで実現できて、数字の部分は length q q XXXX q とかしてやって XXXX の部分を q を含まないキーワードで適当に埋めてやれば OK です。

ただこれは文字数だけ print を呼べば良いからできる方法であって、 eval みたいに文字列を一旦まとめる必要があるとダメ。

小文字アルファベットしばり PerlBase64 しばり Perlでは s//something/e を最初に連打することによって、好きな文字列を作ることができましたー、というのがこれまでのあらすじ。

これを予約語だけでやろうとすると、

s XX chr length ????? XYYY xor ... xor eval

の形になるとして、 X は 1 文字の小文字アルファベット、 YYY は e を含む文字列で正規表現の modifier として valid で、かつ、 XX と XYYY は予約語でないといけない、という条件になります。 XX と同じ文字が並んでる予約語は qq しか無く、 q ではじまって e を含む唯一の予約語である quotemeta は t が modifier として不適切です。

ああ、詰んだな、と放置してました。今回気付いたのは、1回の正規表現置換で無理なら2回やれば無理かなーということで、

s qq q
s X X chr length ????? XYYY

の形、つまり最初に空白を足しておいてから、その空白を本命の文字に変えれば良いのではないかと。 X が連続してなくて良いので、 X の候補が m, q, s, x, y の5つに増えます。最初、 X = s, XYYY = sleep がうまくいきそうな気がしたんですが、 e が2つあると2回 eval されちゃって扱いにくいったらありゃしないのでもうちょっと眺めてると、 semop がありました。ありがとう POSIX 。まぁ splice とかも該当すると思いますが。

ここまで来るとあとはやるだけーと思ったのですが、空白を含んだスクリプトがうまく変換できません。一番最初にある空白を次の文字で入れ変えていくので、空白を詰めてしまうので。 print "Hey" だと

___________  # 最初に 11 文字空白を確保(_ で空白を示します)
p__________  # 1文字ずつ空白が置きかわっていく
pr_________
pri________
prin_______
print______
print______  # 空白を空白に置き変えてる
print"_____  # 詰められてしまった
print"H____
print"He___
print"Hey__
print"Hey"_

というように変換されていく感じ。 JS コード中の ppencode_core がこの変換をしてます。

こういう場合はまあ、空白が無い、評価すると空白を含んだ Perl コードになる Perl コードを作って、 2回 eval すれば良い…とのことで例のごとく version string に活躍してもらいました。

print "Hey" だと

$_="";v112.114.105.110.116.32.34.72.101.121.34

というコードを ppencode_core でエンコードして、これを2回 eval すれば良い、と。最初の $_=""; は $_ をいじってるとあまり厳密な変換じゃないな…と思ったからつけただけです。

長い間、「普通にできんじゃねーの?」→「あれ、できないな。。」を定期的に繰り返してたので解決して良かったと言えます

Base64 decoder/encoder in Perl

http://shinh.skr.jp/obf/b64_dec.pl

dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo+
s//v62/e+s//v60/e+s//v44/e+s//v39/e+s//v39/e+s//join/+s//v32/e+s//base64/ss+
s//v95/e+s//decode/+s//v32/e+s//print/+s//v59/e+s//Base64/+s//v58/e+s//v58/e
+s//MIME/+s//v32/e+s//use/s/eval

この Base64 ぽく見える物体は Perl コードで、実行すると引数で指定したファイルに対する base64 デコーダとして機能します。

$ perl b64_dec.pl b64_dec.pl > b64_enc.pl

元々のコードは Base64 でもあったので、 Base64 デコーダによって復元できました。復元されたスクリプトBase64 エンコーダになってます。よって、

$ perl b64_enc.pl b64_enc.pl
dXNlIE1JTUU6OkJhc2U2NDtwcmludCBlbmNvZGVfYmFzZTY0IGpvaW4nJyw8PjsKX19FTkRfXwo+
s//v62/e+s//v60/e+s//v44/e+s//v39/e+s//v39/e+s//join/+s//v32/e+s//base64/ss+
s//v95/e+s//decode/+s//v32/e+s//print/+s//v59/e+s//Base64/+s//v58/e+s//v58/e
+s//MIME/+s//v32/e+s//use/s/eval

で元に戻ります。

Perl は思ったより簡単だったけど、 Ruby で同じことはできない気がしますね…

TLE 2015

http://felicity.iiit.ac.in/contest/tle/

成果物: https://gist.github.com/shinh/64fbd3d43147f6a7bbb4

毎年思ってることだけど、ここ数年は特に脳死しているなーという時に行なわれてる…ので腑甲斐ないのはしょうがないんだよ。

今回は結構神回だったんじゃないかなー実は。たぶん処理速度の問題で SNUSP 問題が消え失せたんじゃないかと思うんだけど、あれ別に面白くなかった気がするから消えて良かったんでないかと思うます。あとたぶんだけどあのへんの問題が決定的な要因になったんでないかな、残っていれば。偶然から生まれた神回感が強い。

あとちなみに初回の延期時から問題は変わってない…気がするので、やろうと思えば2週間以上使えたんじゃないかな実は…僕は延期した日にやったことは SNUSP の仕様読んでたくらいで、他の問題はだいたい忘れて脳死してたので、当日まで準備とかしてないよ…ホントだよ

Distinct Substrings

ゴミみたいな答えができたらゴミを後ろにつけると高得点というすごい問題。主催者が深く考えてない問題が出るのは毎度恒例ではある。トップを取るとかはどうでもいいスコアシステムだったので瞬時に捨てた。

Find the GCD

GCD(P,Q) を求めよただし P,Q には n,m,R を使って色んなことをして作られる。みたいな問題。 P,Q は巨大でかつ問題の制限として四則演算オペレータ使っちゃいけないということなんで、賢いやりかたがあるんだろうなーと思いつつ、はて…としばらく死んでいた。

まさに脳死という感じで。脳死しててもしゃーないんで、とりあえず正の点数取るか、そのうちなんか降ってくるだろ、となるのが私の唯一偉いところだと思ってるので、とりあえず解いた。つまり多倍長整数で mod が計算できる程度のコードを +-*/% 無しで書きました。感想はしんどかった。

手元で通るぽいコードを投げてみると Runtime Error 。これは TLE だろうかなあ…と思いつつデカいケースを喰わせてみると 0.15 秒くらいかかる。 1 秒が TLE の問題でこれだけ時間かかってると、まあだめだろうな…と思いつつ、 gprof とかで最適化するのにムダな時間を使う。本当にムダだ。

本当にムダなので賢いやり方を探そう、まー R てのが与えられてるからこれとなんかすればいいんでしょ?てことで適当に色んなことをやってると、 GCD(P,Q) が GCD(P,2^R) と一致するじゃありませんか…てことで解いた。

翌日 kinaba さんにアレでなんでいいのって聞いたらそんなの当たり前じゃないですか的なこと言われて、いや実際当たり前だった。はい。

Life, the Universe and Everything

数字がいくつか与えられるので、 echo しなさい、で、 42 で出力を止めなさい。ただし -nostdlib ついてるよ、とのこと。

えっとそれは得意分野やんけ…と瞬殺してほったらかしてたけど、普通にゴルフ足りてなかった。

ASCII Weaving

もうこの手の飽き飽きなんだよ…というような、画像圧縮問題。いつも通りとりあえず RLE してそれぽい解ができる。

いつも通りだとこれに huffman かけるんだけど、なんか huffman てやる前からどのくらいのサイズになるか見積れるわけで、はてこれはそういう感じでは mame さんに絶対追いつけないぞ、なんかすごいことが行なわれてる…

ということでやる気が失せて放置。

Halting

C プログラムの停止性を判断せよとの問題。

全部停止するって言えば 4 点入るので、あとは固定解答与えりゃいいやろ…とやってみるとうまくいかないので眺めてみると。出てくる順番が変わってるぽい。

出てくる順番変わってるだけな気がしたけど、まあ限定された C インタプリタとか割とすぐ書けるよね…と思って書きはじめたら全然間に合わなかった。いやまあインタプリタ書けても停止性は判定できないわけだけど、ある程度できたらヒューリスティクス適当に入れりゃ適当に得点高いだろ、って思ってたんだけど…

Reverse Quine

いつも通りの Quine 。 g++ の時は #include のかわりに #import 使うてのは基本中の基本だったはずなのに、脳死しててできなかった。ひどい。

工夫したところは ext/rope を #include すれば stdio.h と string が自動的に手に入る、てことくらいでしょうかね…

まとめ

問題はすごく良かったと思います。が、全体的に脳死してた。そもそも開始時間から大幅に遅れて起きてる時点でアレですね…なんか年々体力とやる気が失われている気がする。それくらいしか取り柄がないというのに…

あとまあ最終版にスコアボード全く見ずに Halting やってたのもひどいなあと思う。自分が1位から落ちてることにすら気付いてたんだっけ…という感じ。もうちょい戦略的にやるべき

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h