PHPでFizzBuzzをできるだけ短く解いてみた
結論
PHPerKaigi2024のコードゴルフ企画でFizzBuzzが出題されたので解いてみました。PHPerKaigi2024の採点システム上ではこのコードが一番短いと思います。これで60bytesだと思います。
for(;@$i++<100;)echo@["Fizz"][$i%3].@["Buzz"][$i%5]?:$i,"
"; //60bytes
本題
こんにちは、notch_manです。先日参加したPHPerKaigi2024でコードゴルフ企画があり、FizzBuzzが出題されたので解いてみました。
提出コードは冒頭の物で何と1位になりました。ただ、手元では57bytesのコードも実装していたのでそれも供養します。
for(;$i++<100;)echo["Fizz"][$i%3].["Buzz"][$i%5]?:$i,"
"; //57bytes
このコードはnoticeやwarningが出てしまい採点システムに弾かれてしまいます。
解説
解説は冒頭のコードで説明していきます。まず、for文でいきなり未定義変数を使っていますが、これは以下の仕様を利用しています。
この仕様はnullを数値として扱うとゼロになるというものです。これを利用してnullの変数に対してインクリメントすることで暗黙の型変換が行なわれ1,2,3,....となるのです。
次にecho@["Fizz"][$i%3].@["Buzz"][$i%5]
の部分です。ここでFizz
/Buzz
/FizzBuzz
の文字列を生成します。FizzBuzzは3で割り切ればFizz
、5で割り切ればBuzz
、15で割り切ればFizzBuzz
を出力します。15で割り切れるは「3で割り切れるかつ5で割り切れる」と等しいのでわざわざ条件式を追加する必要はありません。
ここで普通の発想であれば以下のコードを実装するのではないでしょうか?
($i%3?"":"Fizz").($i%5?"":"Buzz")
実はこの方法で65bytesの回答を得られるのです。これでもかなり短い!!
ここでFizzを出す条件がi%3=0
になるということに注意すれば、配列の未定義領域の参照はnull
になる性質を利用して要素1の配列を使ってFizz
or null
を出すという発想を得られます。Buzz
の場合も同様です。これで三項演算子より短く同等の表現を得ることができます。
自分も三項演算子を使いたくないので頑張って色々考えてようやく気づけたのでこれをすぐに気づくのは難しいと思います(笑)ちょうどuzullaさんやshunsockさんと話しながら配列とか使えないのかな~みたいな雑談をしていて着想を得ました。
これにより配列アクセスにi%3==0 and i%5!=0
の時はFizz.null
でFizz
を得られ、i%3!=0 and i%5==0
の時はnull.Buzz
でFizz
を得られ、i%3==0 and i%5==0
のときはFizz.Buzz
でFizzBuzz
を得られることができます。ちなみに文字列演算においてはnullが空文字列として扱われる性質を利用しています。
両者がnullの時はnull.null
が空文字列になり、エルビス演算子で数値を出すことができます。これで題意を満たせるコードになります。
最後の改行ですが普通であれば\n
を書くと思います。しかし、改行を入力すれば内部ではLFコード0A
が1bytesで格納されるので1トークンで表現することができます。つまり、1bytes削れるわけですね。
学びになったこと
null.null
は文字列になるみたいです。初めて知りましたがPHPは暗黙の型変換がたくさんありますね...。困ったときはgettype
を使ったりPHPマニュアルを見に行きましょう!ちなみにPHPマニュアルには「nullは常に文字列に変換されます」と書かれていました。
@
はエラー制御演算子というもので、式により生成されたエラーメッセージを無視するというものです。初めて知ったし、余程のことが無い限り業務でも使わないですね(笑)
オチ
ChatGPTにも回答させてみました。
<?php
for($i=1;$i<=100;$i++)echo($i%3?($i%5?$i:'Buzz'):($i%5?'Fizz':'FizzBuzz'))."\n";
これは80bytesだと思います。なお、色々プロンプトを入れて頑張ってみましたがどう頑張っても70bytes台が限界みたいです。60bytes台に乗ればChatGPTに勝利したと言っても良いでしょう(笑)
なお、エラー制御演算子を使わない回答は65bytesになります。エラーを潰すのはアウトローな感じがしますね(笑)
for($i=1;$i++<100;)echo($i%3?"":"Fizz").($i%5?"":"Buzz")?:$i,"
"; //65bytes
Discussion
最後を,~ ;
にすれば1バイト減らせると思うのですが、ここのシステムでは駄目だったのでしょうか?誰もやってる人がいなかったのがちょっと気になったので。※空白はchr(245)です
存在しない定数を参照したときにその定数の名前の文字列として評価される仕様は、このシステムで使われている PHP 8.2 では廃止されているため、
chr(245)
の生バイトをただ配置しても、未定義定数のエラーになります。~
を使う場合、クォートで囲って~"<245>"
の 4バイトになってしまうので"<10>"
の 3バイトよりも長くなってしまいます。まじだ!
今試したらエラー吐いた!
前試したときはできていた気がするのでコメント書いたのですが、どうも勘違いしていたようです。失礼しました。