LoginSignup
0
0

More than 1 year has passed since last update.

【 #ゆめみからの挑戦状 ★第5弾】に回答してみました

Posted at

こちらで開催されていた【ゆめみからの挑戦状 第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に追加されていくため、最後にソート処理をする必要が無くなっています。


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0