見出し画像

Code Golf への誘い

最近仕事が終わってからコードゴルフをしています。​ご存知の方も多いでしょう。何を今更と。
今回は知らない/やったことない方向けに紹介する記事を書いてみたいと思います。

---

コードゴルフとは

コードゴルフはコンピュータプログラミング・コンテストの一種。参加者は与えられたアルゴリズムを、可能な限りもっとも短いソースコードで記述することを競う[1]。バイナリサイズではなく、ソースコードの文字数がスコアとなる。

コードゴルフ - Wikipedia

とのことです。

要はいかに少ない文字数でお題を達成するか、という事を競うゲームです。それ専用のサイトもあります。以前は anarchy golf というサイトで腕を振るっていたものですが、今みたら実行画面でエラーが出てて使えない状態でした。悲しいです。現在は Code Golf というサイトで始めています。

大丈夫でしょうか?イメージできてますか?

そうでしょうそうでしょう。まだちょっとよく分からないですよね。理解を深めるために実際にやってみましょう。

---

まず Code Golf で一番回答が集まっている FizzBuzz 問題 について解いてみましょう。

FizzBuzz問題とは、1から任意の数までを列挙するなかで、

- 3 で割り切れる数の時は Fizz
- 5 で割り切れる数の時は Buzz
- 3 でも 5 でも割り切れる数の時は FizzBuzz
- 上記以外はその数字

を出力する問題です。ここでは 1 から 100 を列挙するようになっています。つまり実行して

1
2
Fizz  // <- 3 で割り切れるからFizz
4
Buzz  // <- 5 で割り切れるからBuzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz  // <- 3 でも 5 でも割り切れるからFizzBuzz
16
.
.
. 
// (以下省略)

という出力になれば正解です。

まずはすごく素直に書くとこんな感じでしょうか(PHPerなのでPHPで書きます)。

for ($i = 1; $i <= 100; $i++) {
 if ($i % 3 === 0 && $i % 5 === 0) {
   echo 'FizzBuzz' . PHP_EOL;
 } else if ($i % 3 === 0) {
   echo 'Fizz' . PHP_EOL;
 } else if ($i % 5 === 0) {
   echo 'Buzz' . PHP_EOL;
 } else {
   echo $i . PHP_EOL;
 }
}

実行したところ正解しました。さて、このサイトには文字数(Stroke)に応じたランキングが存在します。少ないほど上位です。
これで何位に入れたでしょうか。

Fizz Buzz in PHP | Code Golf

なんと100位にすら入れませんでした。100位(2020/7/30 時点の同率99位)は 71 文字です。これを超えないとランキングには入れません。上記のコードだと 252 文字なので、大幅に減らさないとですね。少しずつ改善していきます。

for 文を圧縮

for の構文は以下の通りです。

for (初期化式; 継続条件式; 再初期化式) {
   なんらかの処理
}

今まで何の疑いもなくこう書いていたと思います。ですが、なんと、(すくなくともPHPは)各式を省略できます。なので for 文の最短はこう書けます。

for (;;) {
}

これで Syntax Error はでません。しかしこれだと無限ループになってしまうので継続条件くらいは書きましょう。

for (;$i <= 100;) {
}

これで $i が 100 以下の時に {} の中の処理が実行されます。しかし何もしないと $i の値は変わらないのでこれでも無限ループです。

for の評価の順番は

1. 初期化式を評価する
2. 継続条件式を評価する
  → true であれば 3 へ、false であれば終了
3. {} 内を実行
4. 再初期化式を実行
5. 2 へ戻る

を繰り返しています。4 の再初期化式で $i++ と書いても良いのですが、4 の次に必ず 2 の継続条件式がくるので、ここでインクリメントした方が文字数は少なそうです。

for (;++$i <= 100;) {
}

これで100回のループが書けました。

このコードだと 1 をすっ飛ばしていて、変数 $i が継続条件式の ++$i の時に始めて出てきます。$i は初期化されていません。厳しい言語だと間違いなくアウトですが、そこはPHPです。「$i はしらないけど、インクリメントしてるってことは int か何かなんだろ?」と勝手に解釈してくれます。普段疎ましく思っていた言語仕様が Code Golf では強い味方になります。

初期値が0であれば定義しない、というのはすごく使えるテクニックですので覚えておいて損はないと思います。

あと細かいですが、「100以下」と「101未満」は等価ですので

for (;++$i < 101;) {
}

と書いて1文字削れます!あと余分なスペースも削りましょう。

for(;++$i<101;){
}

これで1行目は 31 文字から 14 文字へ 17 文字も削れました。

if 文は使わない!

次にメインの処理をみていきます。if 文は見るからに冗長です。それぞれの処理も echo ..... PHP_EOL; とだいたい同じっぽい。こういう時は三項演算子を使うのが常套手段です。

PHPだと

条件式 ? 式1 : 式2

のとう構文で書きます(他の言語もだいたいそうですが)。条件式が true の場合は式1が、false の場合は式2が評価されます。分かり易い例で書くと

if ($a === 1) {
  echo '$a は1です!';
} else {
  echo '$a は1じゃないです!';
}

というコードは

echo $a === 1 ? '$a は1です!' : '$a は1じゃないです!';

と1行で書けます。さらにはネストも可能で、

if ($a === 1) {
  echo '$a は1です!';
} else if ($a === 2) {
  echo '$a は2です!';
} else {
  echo '$a は1でも2でもないです!';
}

これが

echo $a === 1 
  ? '$a は1です!' 
  : (
    $a === 2 
      ? '$a は2です!' 
      : '$a は1でも2でもないです!'
    );

となります(分かり易い様に改行を入れました)。

元のコードに戻って、これを三項演算子で書くとこうなります。

  echo $i % 3 === 0 && $i % 5 === 0
   ? 'FizzBuzz' . PHP_EOL
   : (
     $i % 3 === 0
       ? 'Fizz' . PHP_EOL
       : (
         $i % 5 === 0
           ? 'Buzz' . PHP_EOL
           : $i . PHP_EOL
       )
   );

どうでしょう。読めたものではないですね。大丈夫です。Code Golfとはこういうものです。

そして改行を表す PHP_EOL。これも何度も出てきて文字数を食っています。今回は必ず最後が改行になるので纏めてこうしましょう。

  echo ($i % 3 === 0 && $i % 5 === 0
   ? 'FizzBuzz'
   : (
     $i % 3 === 0
       ? 'Fizz'
       : (
         $i % 5 === 0
           ? 'Buzz'
           : $i
       )
   )
 ) . PHP_EOL;

ちょっとスッキリしましたかね。これを一行にまとめると、

echo ($i%3===0&&$i%5===0?'FizzBuzz':($i%3===0?'Fizz':($i%5===0?'Buzz':$i))).PHP_EOL;

となりました。

もっと削ろう!

ここまでを for 文と合わせると

for(;++$i<101;){
 echo ($i%3===0&&$i%5===0?'FizzBuzz':($i%3===0?'Fizz':($i%5===0?'Buzz':$i))).PHP_EOL;
}

となり、105 文字。半分以上減らせました!ただ 71 文字へはまだまだです。もっと減らせるところは無いでしょうか。まずインデントを消します。

for(;++$i<101;){
echo ($i%3===0&&$i%5===0?'FizzBuzz':($i%3===0?'Fizz':($i%5===0?'Buzz':$i))).PHP_EOL;
}

1文字減りました。

次に、for 文は {} を省略できます。何でもかんでも省略できる訳ではなく、中の処理が 1 ステートメントの時に省略可能です。改行も取れちゃいます。

for(;++$i<101;)echo ($i%3===0&&$i%5===0?'FizzBuzz':($i%3===0?'Fizz':($i%5===0?'Buzz':$i))).PHP_EOL;

4文字減りました。

割り切れるかどうかを判定している式は厳密等価演算子である必要はないので === を == に置き換えましょう。

for(;++$i<101;)echo ($i%3==0&&$i%5==0?'FizzBuzz':($i%3==0?'Fizz':($i%5==0?'Buzz':$i))).PHP_EOL;

はい、4 文字消えました。

PHP_EOL って長くないですか?定数を使う必要もないので"\n" に置き換えましょう。

for(;++$i<101;)echo ($i%3==0&&$i%5==0?'FizzBuzz':($i%3==0?'Fizz':($i%5==0?'Buzz':$i)))."\n";

ちなみに以下でも同じ文字数になります。\n は1文字扱いの様です。

for(;++$i<101;)echo ($i%3==0&&$i%5==0?'FizzBuzz':($i%3==0?'Fizz':($i%5==0?'Buzz':$i)))."
";

echo と ( の間のスペースもなくて良さそうです。

for(;++$i<101;)echo($i%3==0&&$i%5==0?'FizzBuzz':($i%3==0?'Fizz':($i%5==0?'Buzz':$i)))."\n";

ここまでで 91 文字、あと 20 文字!



といったところですが、ここまでにしておきましょう。このコードはもっともっと短くできます。これから先は自身で試行錯誤するのがいいと思います。ちなみに私は 64 文字まで減らしました。トップの人は 52 文字です。異次元。

最後に、当然実務でこの様なコードを書いてはいけません。でも極端な読みづらい書き方を知っていれば、逆に可読性の高いコードとはどの様なものかが見えてくると思いませんか?そういった訓練のためにもCode Golfは有用だと思いますのでみなさま奮ってご参加ください。Let's golfing!





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