【 #ゆめみからの挑戦状 ★第5弾】に挑戦 67バイト

最近は地球を防衛しつつ戦うために生きつつ床を塗り潰しつつアイドルをプロデュースするのにとても忙しい。

https://twitter.com/yumemiinc/status/1567724142811820033

PHPだったので挑戦してみることにします。


# 問題

入力`$in`を、出力`$out`に変換します。

```php
// 入力
$in = [
    ['2nd' => 'two', 'four' => '4th'],
    'three' => '3rd',
    ['one' => '1st'],
    '10th' => 'ten',
    ['6th' => 'six'],
    '5th' => 'five',
    'seven' => '7th',
    ['fourteen' => '14th', '11th' => 'eleven'],
    ['8th' => 'eight'],
    'thirteen' => '13th',
    '12th' => 'twelve',
    'nine' => '9th',
    ['15th' => 'fifteen'],
];

// 出力
$out = [
    '1st' => 'one',
    '2nd' => 'two',
    '3rd' => 'three',
    '4th' => 'four',
    '5th' => 'five',
    '6th' => 'six',
    '7th' => 'seven',
    '8th' => 'eight',
    '9th' => 'nine',
    '10th' => 'ten',
    '11th' => 'eleven',
    '12th' => 'twelve',
    '13th' => 'thirteen',
    '14th' => 'fourteen',
    '15th' => 'fifteen',
];
```

ここではバイト数を、変数`$out`に適切な値を突っ込むまでのカウントとします。
出力は`var_dump`でも`var_export`でも`print_r`でも好きなものを使ってもらうということで。

# 78バイト

```php:78バイト (84バイト >= PHP8.0)
for(;$i++<15;$out[($s=numfmt_create)(en,6)->format($i)]=$s(en,5)->format($i));       // PHP <  PHP8.0
for(;$i++<15;$out[($s='numfmt_create')('en',6)->format($i)]=$s('en',5)->format($i)); // PHP >= PHP8.0

var_export($out);
```

https://3v4l.org/v62OQ

`$in`？
なにそれ？


# 解説

[NumberFormatter](https://www.php.net/manual/ja/class.numberformatter.php)は、数値をロケールに沿った文字列に変換してくれるフォーマッタです。

```php
echo (new NumberFormatter('ja', NumberFormatter::SPELLOUT))->format(123456);
  // 十二万三千四百五十六

echo (new NumberFormatter('en', NumberFormatter::SPELLOUT))->format(123456);
  // one hundred twenty-three thousand four hundred fifty-six

echo (new NumberFormatter('fr', NumberFormatter::SPELLOUT))->format(123456);
  // cent vingt-trois mille quatre cent cinquante-six

echo (new NumberFormatter('de', NumberFormatter::SPELLOUT))->format(123456);
  // ein-hundert-drei-und-zwanzig-tausend-vier-hundert-sechs-und-fünfzig
```

便利ですね。

[コンストラクタ](https://www.php.net/manual/ja/numberformatter.create.php)の第1引数`$locale`でロケール(≒言語)を、第2引数`$style`で[出力フォーマット](https://www.php.net/manual/ja/class.numberformatter.php#intl.numberformatter-constants.unumberformatstyle)を指定します。
定数値5は`NumberFormatter::SPELLOUT`を表し、英語の場合`one/two/three`みたいな形、定数値6は`NumberFormatter::ORDINAL`で`1st/2nd/3rd`みたいな形になります。
定数値はPHPバージョンによって異なる可能性があるので、通常は定数名で指定するべきです。

これだけ知っていれば問題文の出力は全て作れるので、入力を使う必要がなくなりました。
あとはなるべく短くしていきましょう。

`NumberFormatter::create()`より`numfmt_create()`のほうが短いのでそちらを使います。
それでも長いですが、PHPは`$f='numfmt_create';$f()`みたいに変数値から関数を呼べるので、複数回使う場合は変数に入れると短くなります。
実は変数値だけではなく`'numfmt_create'()`と文字列からでも直で呼べるんだけど、これの使い道は今のところ思いついていません。
未定義定数値が定数名と同じになる書き方は[PHP8.0で死んだ](https://qiita.com/rana_kualu/items/c110cb244c3ee38c6859#access-to-undefined-constants-now-always-results-in-an-error-exception)ので、PHP8.0以降はクォートで囲むのが必須です。
まあそれ以前もE_WARNINGだったので、元々使ってはいけない書き方でしたが。

ということで、PHP7で78バイト、PHP8で84バイトが私の考えつく限界でした。
これ以上いけますかね？


もちろんこの回答は問題文に関わらず出力が固定なので、設問が少し変わるだけで動かなくなります。
コードゴルフ以外で使ってはダメな解法です。


# まともに解く

流石にこれだけだとアレなので、問題文が変更されても動作する汎用的な解法も置いておきます。
こちらは特にショートコーディングとか考えず普通に解きました。
あとエラーも出ないようにします。

```php
// そのいち
$out = iterator_to_array(new RecursiveIteratorIterator(new RecursiveArrayIterator($in)), true);
foreach ($out as $k=>$v) {
    if (is_numeric($v[0])) {
        $out[$v] = $k;
        unset($out[$k]);
    }
}
ksort($out, \SORT_NUMERIC);
var_export($out);

// そのに
$out = [];
array_walk_recursive($in, function ($v, $k) use (&$out) {
    is_numeric($v[0]) ? $out[$v] = $k : $out[$k] = $v;
});
ksort($out, \SORT_NUMERIC);
var_export($out);
```

すごい普通だ。
普通すぎてみんな書いてる書き方なので面白くもないですね。

そのいちは配列を手動でフラットにしたあとで、入れ替えが必要な項目のキーと値を入れ替えています。
そのにはフラット化と入れ替えを同時に行っています。

ただ[foreach](https://www.php.net/manual/ja/control-structures.foreach.php)や[array_walk_recursive](https://www.php.net/manual/ja/function.array-walk-recursive)とかは何故かキーにリファレンスが使えないので、何気にキーの書き換えが面倒だったりします。
`array_walk_recursive($in, function (&$v, &$k){ is_numeric($v[0]) && [$k, $v] = [$v, $k]; });`とか書きたいですよね。

いや、ゴルフ以外でこんなの書きたいか？


# 70バイト

2022/09/14更新。78バイト → 70バイト

実は[date](https://www.php.net/manual/ja/datetime.format.php)でも"1st"って文字列を作れることに気がついた。


```php:70バイト
for(;$i<15;$out[date(jS,$i++*86400)]=numfmt_create(en,5)->format($i));
```

https://3v4l.org/ujVUS#v7.4.30

# 68バイト

2022/09/15更新。70バイト → 68バイト

https://twitter.com/nsfisis/status/1570100731436470275

まじかよ。
そこまで考えが及ばなかったぜ…凄い！

```php:68バイト
for(;$i<15;$out[date(jS,$i++*9e4)]=numfmt_create(en,5)->format($i)); 
```

https://3v4l.org/V9nct#v7.4.30

# 67バイト

2022/11/14更新。68バイト → 67バイト

```php:67バイト
for(;$i<15;$out[date(jS,$i++*9e4)]=numfmt_create(1,5)->format($i));
```

https://3v4l.org/tNRAg

1文字減らせた。

[numfmt_create](https://www.php.net/manual/ja/numberformatter.create.php)の第一引数`$locale`に無効な値を与えると、3v4lでは`en`として扱われました。
めでたし。

なお、この出力は環境によって変わります。
手元のWindowsでは日本語になりました。

ちなみに無効な値を与えたときに使われるデフォルト値は`intl.default_locale`ではないようです。
手元のWindowsでは`intl.default_locale=en_US`しようが`locale_set_default('eu-US')`しようが`setlocale(LC_ALL, 'en')`しようが日本語になりました。
いったいどこの値を見ているんだこれ。