最近は地球を防衛しつつ戦うために生きつつ床を塗り潰しつつアイドルをプロデュースするのにとても忙しい。
PHPだったので挑戦してみることにします。
問題
入力$in
を、出力$out
に変換します。
// 入力
$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バイト
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);
$in
?
なにそれ?
解説
NumberFormatterは、数値をロケールに沿った文字列に変換してくれるフォーマッタです。
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
便利ですね。
コンストラクタの第1引数$locale
でロケール(≒言語)を、第2引数$style
で出力フォーマットを指定します。
定数値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で死んだので、PHP8.0以降はクォートで囲むのが必須です。
まあそれ以前もE_WARNINGだったので、元々使ってはいけない書き方でしたが。
ということで、PHP7で78バイト、PHP8で84バイトが私の考えつく限界でした。
これ以上いけますかね?
もちろんこの回答は問題文に関わらず出力が固定なので、設問が少し変わるだけで動かなくなります。
コードゴルフ以外で使ってはダメな解法です。
まともに解く
流石にこれだけだとアレなので、問題文が変更されても動作する汎用的な解法も置いておきます。
こちらは特にショートコーディングとか考えず普通に解きました。
あとエラーも出ないようにします。
// そのいち
$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や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でも"1st"って文字列を作れることに気がついた。
for(;$i<15;$out[date(jS,$i++*86400)]=numfmt_create(en,5)->format($i));
68バイト
2022/09/15更新。70バイト → 68バイト
まじかよ。
そこまで考えが及ばなかったぜ…凄い!
for(;$i<15;$out[date(jS,$i++*9e4)]=numfmt_create(en,5)->format($i));
67バイト
2022/11/14更新。68バイト → 67バイト
for(;$i<15;$out[date(jS,$i++*9e4)]=numfmt_create(1,5)->format($i));
1文字減らせた。
numfmt_createの第一引数$locale
に無効な値を与えると、3v4lではen
として扱われました。
めでたし。
なお、この出力は環境によって変わります。
手元のWindowsでは日本語になりました。
ちなみに無効な値を与えたときに使われるデフォルト値はintl.default_locale
ではないようです。
手元のWindowsではintl.default_locale=en_US
しようがlocale_set_default('eu-US')
しようがsetlocale(LC_ALL, 'en')
しようが日本語になりました。
いったいどこの値を見ているんだこれ。