【PHP】array_map 関数で使うコールバック関数に配列のキーを渡す

  • 2021年1月25日
  • 2021年1月25日
  • PHP

要約

// 配列の添え字が 0 からの連番の場合このスニペットを使うと便利
array_map(
    fn($value, $index) => $indexと$valueを使ったコールバック関数の処理本体,
    $arr, array_keys($arr)
);
// 配列の添え字がいかにも連想配列な場合このスニペットを使うと便利
/**
 * キーと値を参照するコールバック関数で array_map 的動作を行う
 * @param  array     $arr 変更元の配列
 * @param  callable  $callBack ($value, $key) => any
 * @return array
 */
function array_map_with_key(array $arr, callable $callBack){
    $newArr = [];
    foreach ($arr as $key => $value){
        $newArr[$key] = $callBack($value, $key);
    }

    return $newArr;
}
// 使用例
$nameMap = [
    'hoge' => 'fuga',
    'foo' => 'bar',
    'fizz' => 'bazz'
];
array_map_with_key_foreach($nameMap, fn($value,$key) => $key.'_'.$value);// キーと値の文字列を'_'で結合

本文

 array_map 関数は配列の各要素に array_map 関数に与えたコールバック関数を適用した結果の配列を返す関数です。
PHP: array_map – Manual

<?php
/** @see https://www.php.net/manual/ja/function.array-map.php */

/**
 * $n の三乗を返す
 * @param  int|float  $n
 * @return int|float
 */
function cube($n)
{
    return ($n * $n * $n);
}

$a = [1, 2, 3, 4, 5];
$b = array_map('cube', $a);
print_r($b);
/**
 * Array
(
[0] => 1
[1] => 8
[2] => 27
[3] => 64
[4] => 125
)
 */
// 次の様に無名関数をコールバック関数として渡すこともできます
$a = [1, 2, 3, 4, 5];
$b = array_map(function ($n) {
    return ($n * $n * $n);
}, $a);
print_r($b);// ↑と同じ結果

// PHP8 以降ならば次の様にも書けます
$a = [1, 2, 3, 4, 5];
$b = array_map(fn ($n) => ($n * $n * $n), $a);
print_r($b);// ↑と同じ結果

 この様に配列の各要素を関数に通すことができるのですが、この使い方のみではままあるケースであるキーに応じて何々をする、というケースへの対応が面倒です。
 これに対応する素朴な実装は次です。

<?php
// それぞれの値の文字列にキーの文字列を付与する

$nameMap = [
    'hoge' => 'fuga',
    'foo' => 'bar',
    'fizz' => 'bazz'
];
$concatMap = [];
foreach ($nameMap as $key => $name){
    $concatMap[$key] = $key.'_'.$name;
}
print_r($concatMap);
//Array
//(
//    [hoge] => hoge_fuga
//    [foo] => foo_bar
//    [fizz] => fizz_bazz
//)

// array_map 的に使えるように抽象化すると
/**
 * キーと値を参照するコールバック関数で array_map 的動作を行う
 * @param  array     $arr 変更元の配列
 * @param  callable  $callBack ($value, $key) => any
 * @return array
 */
function array_map_with_key(array $arr, callable $callBack){
    $newArr = [];
    foreach ($arr as $key => $value){
        $newArr[$key] = $callBack($value, $key);
    }

    return $newArr;
}
// 使用例
array_map_with_key_foreach($nameMap, fn($v,$k) => $k.'_'.$v);

 foreach で全要素を回して、全要素についてキーと値の処理をします。どこかにグローバルな関数、あるいは配列用のヘルパー関数群をおける環境ならばこれを置けばよいでしょう。コールバック関数さえわかればそう難しい処理でないので、後からコードを読む人も読みやすいです。

 PHP の配列関数を使ったコードが次で、これを使うと一つの式として処理を記述できます。

$nameMap = [
    'hoge' => 'fuga',
    'foo' => 'bar',
    'fizz' => 'bazz'
];
$concatMap = array_combine(array_keys($nameMap), array_map(fn($v,$k) => $k.'_'.$v, $nameMap, array_keys($nameMap)));
print_r($concatMap);// これまでと同じ

 PHP の配列関数をいくらか把握する必要がある書き方ですが、こういう書き方もできます。コメントをつけて解体すると次の様になります。ネストの内側から外側に向かって読むと比較的わかりやすいです。

$nameMap = [
    'hoge' => 'fuga',
    'foo' => 'bar',
    'fizz' => 'bazz'
];
$concatMap = array_combine(// 第一引数の配列をキー、第二引数の配列を値として配列を作ることで変換元配列のキーを復元する
    array_keys($nameMap),// array_map 関数の返り値のキーとなる配列。元のキーを復元
    array_map(// array_map 関数は第二引数以降の各配列の要素をもとにコールバック関数を動作させます
        fn($v,$k) => $k.'_'.$v,// 配列の各要素に適用するコールバック関数
        $nameMap, // 変換元配列の値を array_map 関数に渡す
        array_keys($nameMap) // 変換元配列のキーを array_map 関数に渡す
    )
);
print_r($concatMap);// これまでと同じ

PHP: array_combine – Manual
PHP: array_keys – Manual
 有意な差が出る計測対象を用意できませんでしたが、後者の配列関数を使った実装は直に C 言語を使えるためものによっては後者のほうがパフォーマンスが良いかもしれません。ループ回数が多いので悪いかもしれません。
 もし、変換元配列のキーが元から 0 番からの連番ならば復元が不要なので次の様に書けてすっきりします。またループが減るのでこちらはパフォーマンスが良さそうです。配列の要素数が非常に多い時はループの呼び方だけで案外処理時間が変わるのでそういった時には試してみるといい結果が出るかもしれません。

$concatMap =array_map(// array_map 関数は第二引数以降の各配列の要素をもとにコールバック関数を動作させます
    fn($v,$k) => $k.'_'.$v,// 配列の各要素に適用するコールバック関数
    $nameMap, // 変換元配列の値を array_map 関数に渡す
    array_keys($nameMap) // 変換元配列のキーを array_map 関数に渡す
);
// コメントなしで
$concatMap = array_map(
    fn($v,$k) => $k.'_'.$v,
    $nameMap, array_keys($nameMap)
);

 余談ですが array_map は複数の配列の同じ順番目の要素を同時に扱えます。これを使う次の様な処理もすっきり書けます。

<?php
$arabic = [1, 2, 3];
$kanji  = ['一', '二', '三'];
$roman  = ['I','II','III'];

$msgList = array_map(static function($a,$k,$r){
    return 'アラビア数字の '.$a.' は漢数字で '.$k.' ローマ数字で '.$r;
},$arabic,$kanji,$roman);
print_r($msgList);
/*
Array
(
    [0] => アラビア数字の 1 は漢数字で 一 ローマ数字で I
    [1] => アラビア数字の 2 は漢数字で 二 ローマ数字で II
    [2] => アラビア数字の 3 は漢数字で 三 ローマ数字で III
)
*/

 言語ファイル等、何かの都合で同じ形状の配列だが全く別のところにある複数の配列を使って何かをしたい時は array_map が最も適しています。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG