見出し画像

PHPerKaigi 2024 で登壇 && コードゴルファーしました

ブログを書くまでが PHPerKaigi

ブログを書くまでが PHPerKaigi ということで書いていきます。

ランチマップ

PHPerKaigi 向けに中野のランチマップ(主にラーメン)を作りました。
無鉄砲とか,博多風龍とか長浜やとか昔はあったんですが閉店してしまっているようで悲しいなと思いました。

登壇

PHPerKaigi 2024 の Day2 に「RubyVM を PHP で実装する 〜Hello World を出力するまで〜」というタイトルで登壇していました。

PHP カンファレンス 2023 でも同じタイトルで登壇させていただいたのですが,差分としては Ruby 3.3 に対応しているという点です。

Ruby はクリスマスにバージョンが上がるためです。もちろん追従するスタイルです。

結構ウケてくれた部分もあり,また,Ruby に詳しいスタッフやオーディエンスの方が集まったりしてカオス感はありましたが,私自身も勉強になる質疑応答で,登壇者側としてもとても体験が良かったなと思います。

やっぱり,ギークな方たちに囲まれるのは楽しいなと感じます。

Ask the Speaker のコーナーでは,色んな方に話かけていただけて良かったです。

コードゴルファー

さて,コードゴルフです。2019 年の PHP カンファレンス福岡では見事に優勝を果たしたわけですが…

2019 年同様に,PHPerKaigi 2024 でもコードゴルファーとして産声をあげた私ですが,1, 2 問目は捨て 3 問目に集中するという戦略を取るも,残念ながら 2 着でした。1 着の takaram さんに 10 文字差という大きなビハインドで負けてしまいました。

どういうコードを書かれていたのかを拝見すると,263 bytes です。

// 引用: https://t.nil.ninja/phperkaigi/2024/golf/q/brainf-ck/a/101/
$s=fread(STDIN,9999);$p=-1;function l($s,&$p,$r){for($d=1;$d+=["["=>$r,"]"=>-$r][$s[$p+=$r]]??0;);}for($m=[$t=0];$s[++$p]??0;){$a=&$m[$t];match(ord($s[$p])){62=>$t++,60=>$t--,43=>$a++,45=>$a--,46=>print chr($a),91=>$a||l($s,$p,1),93=>$a&&l($s,$p,-1),default=>0};}

ord で文字数減らす戦略かー!その手があったかー!っていうのがすごく発見でした。純粋にスゲー!って感動していました。

で,私が書いたコードです。

for($s=fread(STDIN,9999),$q=0,$m=[],$p=0;$q<strlen($s);$q++)for(match($c=$s[$q]){'>'=>$p++,'<'=>$p--,'.'=>print chr($m[$p]),'+'=>@++$m[$p],'-'=>--$m[$p],default=>$d=1},$b=!!@$m[$p],$a=$c=='[';$a||$c==']'and!$b&&$a||$b and!$a&&$d>0;$o=$s[--$q],$d+=$o=='['?-1:($o==']'?1:0));

$m=[] と $m[…] をなんとか消せないかと考えていたんですが,いやぁ参照使うのはなるほどなーって思いました。


2024/3/10 12:58 追記

という不具合があったので直しました。242 bytes です。

for($s=fread(STDIN,9999),$p=-1,$m=[$t=0];$a=&$m[$t],$s[++$p]??0;)for($d=1,match($o=ord($s[$p])){62=>$t++,60=>$t--,43=>$a++,45=>$a--,46=>print chr($a),default=>0};($o==91&&!$a&&$r=1or$o==93&&$a&&$r=-1)&&$d+=["["=>$r,"]"=>-$r][$s[$p+=$r]]??0;);

2024/3/10 11:15 追記
237 bytes まで削れました。

for($s=fread(STDIN,9999),$m=[$t=0];$a=&$m[$t],$s[@++$p]??0;)for($d=1,match($o=ord($s[$p])){62=>$t++,60=>$t--,43=>$a++,45=>$a--,46=>print chr($a),default=>0};($o==91&&!$a&&$r=1or$o==93&&$a&&$r=-1)&&$d+=["["=>$r,"]"=>-$r][$s[$p+=$r]]??0;);

2024/3/10 10:45 追記
takaram さんのコードもう少し削れそうだなと,結果発表のとき見てて思ったんですが,252 bytes くらいまで削れました。

for($s=fread(STDIN,9999),$m=[$t=0];$a=&$m[$t],$s[@++$p]??0;$l=function($r)use(&$p,$s){for($d=1;$d+=["["=>$r,"]"=>-$r][$s[$p+=$r]]??0;);},match(ord($s[$p])){62=>$t++,60=>$t--,43=>$a++,45=>$a--,46=>print chr($a),91=>$a||$l(1),93=>$a&&$l(-1),default=>0});

さて,どういうことを意識してゴルフしているのかというのを懇親会などで聞かれたので,考えをまとめてみます。

PHP という言語のコードゴルフで重要なのは以下です:

ワンライナーで書くことを意識する

echo は文ですが print は式という性質を用いて match のような文を受け付けないシンタックスに組み込むなど,式で書くことを意識するのが PHP のゴルフでは重要です。

以下は書けない:

match ($v) { 'xxx' => echo "..." }

以下は書ける:

match ($v) { 'xxx' => print "..." }

ただ,一方で echo が役立つときもあります。そう,echo はカンマで区切ることで式を分けることができるんです。三項演算子を切り上げたいときなどに使えます。

例えば,以下のようなコードに改行を三項演算子の結果の両者に入れたいケースです。

echo ($i % 15 ? $i : "FizzBuzz") . "
";

このように ( と ) が非常に無駄な文字数になってしまいます。カンマで区切ることで,この三項演算子の式をいい感じのところで区切ってくれるのです。

echo $i % 15 ? $i : "FizzBuzz","
";

2 文字省略できましたね。このような PHP 独特の仕組みを理解しておくとコードゴルフに便利です。

初期化など野暮ったいことはしない。エラー制御で静かにさせる。

PHP では変数が未定義の状態で何かしらの操作を行うと Undefined variable が出ます。例えば以下です。

for (; $i++<100;) {
    // ...
}

$i が未定義というエラーになってしまい $i=0 のような初期化を入れないといけないかと思いますが,@ エラー制御を用いればエラーが出力されなくなります。

for (; @$i++<100;) {
    // ...
}

本来 3 文字かかるはずだったところが 1 文字増えるだけで済みます。FizzBuzz のコードゴルフとかでよく見かけますね。

{ と }, ( と ) を極力書かないことを意識する。

for ($i = 0; $i < 100; $i++) {
    for ($k = 0; $k < 100; $k++) {
        if ($k % 2 === 0) {
            echo "Hello World!";
        }
    }
}

上記のようなコードでは {, } だけで 4 文字も消費しており,無駄です。

for ($i = 0; $i < 100; $i++) 
   for ($k = 0; $k < 100; $k++)
        echo $k % 2 ? '' : "Hello World!";

このようにすることで,{, }  や (, ) の文字数を削ることができます。
比較も右オペランド(オペレーターである === と 0 の部分)は書かないように意識することも重要です。

なるべく式かつ左オペランドだけで完結するように書けるとゴルフできます。

上記の 3 問目で私が書いたコードの

$a||$c==']'and!$b&&$a||$b and!$a

は割と気に入っていて,本来 ( と ) で評価順序を決める必要があるものを,&& と and, || と or の実行の評価順序を意識して書くことで,( と ) を省略しても期待した評価順序通りに動かしています。

文字列,配列など 2 文字を無駄に消費しない。数値型,真偽型でなるべく表現する。

文字列の場合 'a' と最小 3 文字ですが,数値,真偽値の場合 1 文字で済みます。

変数をなるべく使わない

PHP は変数を表すシンタックスでは $ が必ず必要になり,最小文字数は 2 文字です。そのため,変数をなるべく使わないということで文字数の節約になります。

テストケースのハックをする

例えば今回の例は stream_get_contents(STDIN) というのがありましたが,これだけで 26 文字も使います。

これ以上なにか削れないか方法を探すのですが,最終的に思いついたのは fread(STDIN,9999) でした。 17 文字になって 9 文字削れます。

9999 については,PI みたいな 2 文字くらいでクソデカ数値の定数ないかなと探したんですが,なかったです。残念。

10,000 文字以上がテストケースで来ない前提で,破綻してるっちゃしてるので,テストケースのハックです。

fgets(STDIN) でワンチャンないかなと狙ってたんですが,駄目でした。


そんなこんなで,PHPerKaigi 2024 めちゃめちゃ楽しめました。スタッフ,スポンサー,参加者の皆さま,ありがとうございました。

この記事が気に入ったらサポートをしてみませんか?