PHPerKaigi 2024 コードゴルフを解く (DAY 0)

# PHPerKaigi 2024 ⛳️

コードゴルフを解きました。

-----

この記事はQiitaのスライドモードで作ってるよ！
DAY 1以降では僕より短い回答いっぱい出てたので、最短を狙うための記事はもっとほかの人が自分で書いてるのでは！

-----

以後、バイト数は行頭インデントと改行を抜いて計測します。

-----

# 問題 #1 FizzBuzz

------

![スクリーンショット 2024-03-07 1.35.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4121/08662d1b-669c-7008-c00d-818b722bd408.png)

------

標準回答 (182バイト)

```php
for ($i = 1; $i <= 100; $i++) {
  echo match ([$i % 3 === 0, $i % 5 === 0]) {
    [true, true] => "FizzBuzz",
    [true, false] => "Fizz",
    [false, true] => "Buzz",
    [false, false] => $i,
  }, PHP_EOL;
}
```

------

# PHP8らしい、美麗なコードですね…

------

バイト数勝負なので… (126バイト)

```php
for($i=1;$i<=100;$i++) {
  echo match([!($i%3),!($i%5)]){
    [!0,!0]=>"FizzBuzz",
    [!0,!1]=>"Fizz",
    [!1,!0]=>"Buzz",
    default=>$i,
  },"\n";
}
```
------

全部バッドノウハウ

* スペースは無駄なので全部詰める
* 「余りの結果は0か0以外か」は`!($i%3)`でboolに
* `true`/`false`も長すぎるので`!0`/`!1`に置き換え可能
* PHPの制御構造は中身が単文なら `{}` 省略可能

------

# まだいける

------

# ご存じでしたか？

 * PHPの配列キーに使えるのはintとstringだけ
 * boolで配列アクセスすると`true`/`false`に変換される

------

配列にする (118バイト)

```php
for ($i=1;++$i<101;) {
  echo [
    0 => [0 => $i, 1 => 'Buzz'],
    1 => [0 => 'Fizz', 1 => 'FizzBuzz'],
  ][!($i%3)][!($i%5)],"\n";
}
```

------

配列の`0 =>` `1 =>` は省略 (87バイト)

```php
for ($i=1;++$i<101;) {
  echo [
    [$i, 'Buzz'],
    ['Fizz', 'FizzBuzz']
  ][!($i%3)][!($i%5)],"\n";
}
```

------

ぎゅっと縮めて

```php
for($i=1;$i<=100;$i++)
  echo [[$i,'Buzz'],['Fizz','FizzBuzz']][!($i%3)][!($i%5)],"\n";
```

------

80バイト

```php
for($i=1;$i++<101;)echo[[$i,'Buzz'],['Fizz','FizzBuzz']][!($i%3)][!($i%5)],"\n";
```

------

別解 (86バイト)

```php
echo join("\n", array_map(
  fn($i) => ($i%3 ? '' : 'Fizz')
    . ($i%5 ? '' : 'Buzz') ?: $i,
  range(1,100)
));
```

------

別解 (82バイト)

```php
array_map(
  fn($i) => print(($i%3 ? '' : 'Fizz')
    . ($i%5 ? '' : 'Buzz') ?: $i
  )."\n",
  range(1,100)
);
```

-----

# 問題 #2 Base32

------

![スクリーンショット 2024-03-07 2.59.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4121/f5c24c17-f2fd-7465-8c57-2e25d3fd1aab.png)

------

![スクリーンショット 2024-03-07 3.00.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4121/38e64bcd-b327-7ae1-a929-ba18bbcca9a5.png)

------

# php-playを使って解くときのヒント

------

wasmでは`STDIN`は未定義だが、この方法でユーザー定義できる。

```php
$str = "
aaa
bbb
ccc
";

$f = fopen('php://memory', 'rw');
fwrite($f, $str);
rewind($f);
defined('STDIN') or define('STDIN', $f);
```

------

# まず考えたこと

------

長いな…

```php
const TABLE = [
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
  'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
  'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
  'Y', 'Z', '2', '3', '4', '5', '6', '7',
];
```

------

こうして…

```php
$tbl = [...range('A','Z'),...range('2','7')];
```

------

これを…

```php
  foreach (str_split($bits, 5) as $b) {
    $base32 .= TABLE[base_convert(str_pad($b, 5, '0'), 2, 10)];
  }
```

--------

こうじゃ

```php
  foreach (str_split($bits, 5) as $b) {
    $base32 .= [...range('A','Z'),...range('2','7')][
      base_convert(str_pad($b, 5, '0'), 2, 10)
    ];
  }
```

--------

## なぜやるのか

一見長くなってそうに見えるが、 `const TABLE =` と `;` が削れてる

---------

## 次に注目するところ

基数の変換 (2進法⇔10進法)

```php
    $bits .= str_pad(base_convert(strval(ord($line[$i])), 10, 2), 8, '0', STR_PAD_LEFT);
    $base32 .= TABLE[base_convert(str_pad($b, 5, '0'), 2, 10)];
```


---------

## 基数変換

専用関数があるので縮まる

```php
    $bits .= str_pad(decbin(strval(ord($line[$i]))), 8, '0', STR_PAD_LEFT);
    $base32 .= TABLE[bindec(str_pad($b, 5, '0'))];
```

---------

## 型を無視する

専用関数があるので縮まる

```php
    $bits .= str_pad(decbin(ord($line[$i])), 8, 0, STR_PAD_LEFT);
    $base32 .= TABLE[bindec(str_pad($b, 5, 0))];
```

---------

## `sprintf()`を使う

```php
    $bits .= sprintf('%08d', decbin(ord($line[$i])));
    $base32 .= TABLE[bindec(str_pad($b, 5, 0))];
```

---------

## 禁断の定数展開

```php
    $bits .= str_pad(decbin(ord($line[$i])), 0, 0);
    $base32 .= TABLE[bindec(str_pad($b, 5, 0))];
```

----------

## いろいろやって

286バイト

```php
while($l=fgets(STDIN)){$l=chop($l);$t='';for($i=0;$i<strlen($l);$i++)$t.=str_pad(decbin(ord($l[$i])),8,0,0);echo join(array_map(fn($b)=>[...range('A','Z'),...range('2','7')][bindec(str_pad($b,5,'0'))],str_split($t,5))),str_repeat('=',(fn($n)=>($n%8)?0:6-$n/8)(strlen($t)%40))??'',"\n";}
```

-----

# 問題 #3 BrainFuck

-----------

やはりphp-playで開発しやすいようにこうする

```php
$s = defined('STDIN') ? stream_get_contents(STDIN) : '>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++
++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>
++++++++[<++++>-]<+.[-]++++++++++.';
```

これはHello, worldを出力するコード

----------

縮めたらこうなった

```php
$s=fread(STDIN,9999);
$c=$p=0;while($c<strlen($s)){switch($s[$c]){
case'>':$p++;break;
case'<':$p--;break;
case'+':$m[$p]??=0;$m[$p]++;break;
case'-':$m[$p]??=0;$m[$p]--;break;
case'.':echo chr($m[$p]);break;
case'[':if(!($m[$p]??0)){$d=1;while($d>0)match($s[++$c]){'['=>$d++,']'=>$d--,default=>0};}break;
case']':if($m[$p]??0){$d=1;while($d>0)match($s[--$c]){']'=>$d++,'['=>$d--,default=>0};}}$c++;
}
```

----------

`$m[$p]??=0;`の初期化を移動する

```php
$c=$p=0;while($c<strlen($s)){$m[$p]??=0;switch($s[$c]){
case'>':$p++;break;
case'<':$p--;break;
case'+':$m[$p]++;break;
case'-':$m[$p]--;break;
case'.':echo chr($m[$p]);break;
case'[':if(!$m[$p]){$d=1;while($d>0)match($s[++$c]){'['=>$d++,']'=>$d--,default=>0};}break;
case']':if($m[$p]){$d=1;while($d>0)match($s[--$c]){']'=>$d++,'['=>$d--,default=>0};}}$c++;
}
```