こちらで開催されていた【ゆめみからの挑戦状 第5弾】に、私もいくつか回答してみました。
https://twitter.com/yumemiinc/status/1567724142811820033
出題者は@mpywさんで、今回の出題内容は
$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'],
];
という、キーと値の関係も配列の次元も順序もバラバラな連想配列を元に
[
'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',
]
という連想配列を作る、PHPの問題でした。
@mpywさんによるQiitaでの回答紹介記事はこちら。
https://qiita.com/mpyw/items/82994d4ae32256a2d844
twitterでの私の回答の最終版はこちらです。
https://twitter.com/akebi_mh/status/1569616698277523457
このうち、jsonに対して正規表現で何とかする最初の2つについて簡単に解説してみます。
まず、1つめのこれ。
$r = json_decode(
preg_replace('/(\w+)(":")(\d+\w+)|"\d+":{|}/',
'$3$2$1', json_encode($in)).'}', true
);
ksort($r, SORT_NUMERIC);
print_r($r);
投稿後に再度修正して未発表だったこちら(手順はほぼ同じ)で説明します。
$r = json_decode(
preg_replace(
'/(\w+)(":")(\d+\w+)|".":{|}(}?)/',
'$4$3$2$1', json_encode($in)), true);
ksort($r, SORT_NUMERIC);
print_r($r);
これは元の配列をjson化した文字列をpreg_replaceで直に書き換えて、そのままjson_decodeで目的の配列を得ようというものです。
(実際には最後に別途ソートが必要ですが)
元の配列$in
をjson_encodeに通すと
{"0":{"2nd":"two","four":"4th"},"three":"3rd","1":{"one":"1st"},"10th":"ten","2":{"6th":"six"},"5th":"five","seven":"7th","3":{"fourteen":"14th","11th":"eleven"},"4":{"8th":"eight"},"thirteen":"13th","12th":"twelve","nine":"9th","5":{"15th":"fifteen"}}
という文字列になります。
正規表現の /(\w+)(":")(\d+\w+)|".":{|}(}?)/
ですが、これはキーと値が逆になっているペアを入れ替えるための (\w+)(":")(\d+\w+)
と、階層構造をフラット化するための ".":{
と }(}?)
の異なる処理を行わせる組み合わせで、preg_replaceの反復処理ごとに|
で区切られているうちのひとつが動作することになります。
(\w+)(":")(\d+\w+)
は (1文字以上の英数字)(":")(1文字以上の数字と1文字以上の英数字) で、これでfour":"4th
というようにキーと値が 逆に並んでいるもの にマッチし、この場合$1
にはfour
、$2
には":"
、$3
には4th
という文字列が入ります。
これを$3$2$1
の順に並べ替え、4th":"four
とします。
".":{
と(}?)
では、"数値":{
や}
を削除し、多次元配列となっている部分をフラットにしています。
この結果、jsonの文字列は
{"2nd":"two","4th":"four","3rd":"three","1st":"one","10th":"ten","6th":"six","5th":"five","7th":"seven","14th":"fourteen","11th":"eleven","8th":"eight","13th":"thirteen","12th":"twelve","9th":"nine","15th":"fifteen"}
となり、これをjson_decodeすると、キーと値の並びが直され、フラット化された連想配列になるので、最後に ksort
でソートすることで目的の連想配列が完成します。
2つめのこちら
preg_match_all('/((\w+)":)?"((\d+)\w+)"(:"(\w+))?/', json_encode($in), $m);
for ($i = min($m[4]), $max = max($m[4]); $i <= $max; $i++) {
if (($n = array_search($i, $m[4])) !== false) {
$r[$m[3][$n]] = $m[2][$n].$m[6][$n];
}
}
これは、json化した元の配列に対し正規表現のパターンをマッチさせて、その結果の配列を使って目的とする連想配列を構築しようというものです。
ここで使っているパターン /((\w+)":)?"((\d+)\w+)"(:"(\w+))?/
ですが、これはパターン全体が一度にマッチすることはなく、((\w+)":)?"((\d+)\w+)"
の部分でfour":"4th
のようなキーと値が逆なペアに対してマッチ、"((\d+)\w+)"(:"(\w+))?
の部分で2nd":"two
のようなキーと値が正常に並んでいるペアにマッチするようになっています。
中間の"((\d+)\w+)"
の部分はどちらのケースにもマッチします。
この正規表現でpreg_match_all
をかけると、変数$m
の中身は以下のような配列になります。
[
[2] => [
[0] => '',
[1] => 'four',
[2] => 'three',
[3] => 'one',
[4] => '',
[5] => '',
[6] => '',
[7] => 'seven',
[8] => 'fourteen',
[9] => '',
[10] => '',
[11] => 'thirteen',
[12] => '',
[13] => 'nine',
[14] => '',
],
[3] => [
[0] => '2nd',
[1] => '4th',
[2] => '3rd',
[3] => '1st',
[4] => '10th',
[5] => '6th',
[6] => '5th',
[7] => '7th',
[8] => '14th',
[9] => '11th',
[10] => '8th',
[11] => '13th',
[12] => '12th',
[13] => '9th',
[14] => '15th',
],
[4] => [
[0] => 2,
[1] => 4,
[2] => 3,
[3] => 1,
[4] => 10,
[5] => 6,
[6] => 5,
[7] => 7,
[8] => 14,
[9] => 11,
[10] => 8,
[11] => 13,
[12] => 12,
[13] => 9,
[14] => 15,
],
[6] => [
[0] => 'two',
[1] => '',
[2] => '',
[3] => '',
[4] => 'ten',
[5] => 'six',
[6] => 'five',
[7] => '',
[8] => '',
[9] => 'eleven',
[10] => 'eight',
[11] => '',
[12] => 'twelve',
[13] => '',
[14] => 'fifteen',
],
]
($m[0]
、$m[1]
、$m[5]
もありますが、マッチさせた後の処理には不要なので省略)
まず、min($m[4])
とmax($m[4])
でループの開始値と終了値を取得します。
ループ内では何回目のループかを$i
が持っており、$n = array_search($i, $m[4]))
とすることで対象データの配列のインデックスを取得し$n
に持たせます。
ループ1回目であれば$m[4][3]
の値が1
なので、インデックスは3
になります。
そのインデックス3
を元に$r[$m[3][3]] = $m[2][3].$m[6][3]
が実行されます。
$m[3][3]
は1st
、$[2][3]
はone
、$m[6][3]
は空文字列なので、
$r['1st'] = 'one'.'';
となります。
以降、$max
まで繰り返すことで、目的の連想配列を得ることができます。
1つめの処理と比べると少々長めになっていますが、代わりに1st
から順に$r
に追加されていくため、最後にソート処理をする必要が無くなっています。