👨‍💻

phpのarray_mapをphpで実装してみる

2020/12/31に公開

こんにちは(๑╹ω╹๑ )

今日はPHPのarray_mapをPHPで実装してみます!

array_mapの動作仕様は公式リファレンスの通りです!

C言語上でのPHPのarray_map定義を拾って厳密に実装したかったのですが今回は割愛します笑

低レイヤーライクなarray_mapを実装してみる

少しずつ本来の仕様に近づいていく形で実装していきます。

callback関数で許容する値はvalueのみ

基本的には、

  • 高階関数
  • 可変関数
  • 参照渡し
  • 再帰処理
  • 内部ポインタ操作

で実装することになるかと思います。

<?php


function getNextValue(&$array){
    $value = current($array);
    next($array);
    return $value;
}

function isEndValue($array, $value){
    end($array);
    return current($array) === $value;
}

function __array_map($callback, $array) {
    $result = [];
    $value = getNextValue($array);
    $result[] = $callback($value);

    if (!isEndValue($array, $value)) {
        $result = array_merge($result, __array_map($callback, $array));
    }
    return $result;
}

$array = [
    "key1" => "value1",
    "key2" => "value2",
    "key3" => "value3",
];

$callback = function($value){
    return $value . " Hello!";
};


$result = __array_map($callback, $array);
print_r($result);

/*
[
    "value1 Hello!",
    "value2 Hello!",
    "value3 Hello!",
]
*/

低レイヤーライクな実装をするとまずこのようになっていくかと思います。
ですがこの実装だとCallableな関数からの返却値には元々のkeyを維持していません。

元々のkeyを維持する実装に修正してみる

<?php


function getNextValue(&$array){
    $value = current($array);
    $cp_array = $array;
    $key = array_flip($cp_array)[$value];
    next($array);
    return ["key" => $key, "value" => $value];
}

function isEndValue($array, $value){
    end($array);
    return current($array) === $value;
}

function __array_map($callback, $array) {
    $result = [];
    $next = getNextValue($array);
    $key = $next["key"];
    $value = $next["value"];
    $result[$key] = $callback($value);

    if (!isEndValue($array, $value)) {
        $result = array_merge($result, __array_map($callback, $array));
    }
    return $result;
}

$array = [
    "key1" => "value1",
    "key2" => "value2",
    "key3" => "value3",
];

$callback = function($value){
    return $value . " Hello!";
};


$result = __array_map($callback, $array);
print_r($result);

/*
[
    "key1" => "value1 Hello!",
    "key2" => "value2 Hello!",
    "key3" => "value3 Hello!",
]
*/

基本的には参照渡し後にcurrentポインタからvalueを取得するタイミングでkeyを抜き取れば問題ないかと思います。

function getNextValue(&$array){
    $value = current($array);
    $cp_array = $array;
    $key = array_flip($cp_array)[$value];
    next($array);
    return ["key" => $key, "value" => $value];
}

注意する点は $array を参照渡ししているので、
array_flip でkey,valueを反転させる時は

  • ディープコピー後の $arrayarray_flip
  • 二度 array_flip

のどちらかになると思います。

そして配列のサイズはもちろん可変なので成るべく全走査を避けたいところです。

という意図でディープコピーをしています。

ですが、このままだとcallback関数でkeyの走査が出来ません。
それでは本来の array_map の仕様に近づけていきましょう。

callback関数でkeyを走査できるように修正を加えてみる

この辺りまでスムーズにアプローチが定まればあと少しです!

<?php

function getNextValue(&$array){
    $value = current($array);
    $cp_array = $array;
    $key = array_flip($cp_array)[$value];
    next($array);
    return ["key" => $key, "value" => $value];
}

function isEndValue($array, $value){
    end($array);
    return current($array) === $value;
}

function __array_map($callback, $array) {
    $result = [];
    $next = getNextValue($array);
    $key = $next["key"];
    $value = $next["value"];
    $result[$key] = $callback($value, $key);

    if (!isEndValue($array, $value)) {
        $result = array_merge($result, __array_map($callback, $array));
    }
    return $result;
}

$array = [
    "key1" => "value1",
    "key2" => "value2",
    "key3" => "value3",
];

$callback = function($value, $key){
    return $value . " " . $key;
};


$result = __array_map($callback, $array);
print_r($result);

/*
[
    "key1" => "value1 key1",
    "key2" => "value2 key2",
    "key3" => "value3 key3",
]
*/

PHPでは、

call先に明示されている引数の個数 < call元に明示されている引数の個数

上記のような事象下では Warning 扱いとはならないので、

$result[$key] = $callback($value, $key);

このように $key をinjectionさせてあげれば単純に実装出来ます。

$callback = function($value, $key){
    return $value . " " . $key;
};

ですので、callback関数側でこのような実装となっていても、

$callback = function($value){
    return $value . " Hello!";
};

のような実装となっていても想定通りに動作します。

そして array_map の第一引数に割り当てるCallableな関数では、
走査後のvalueを返却するため理に叶った実装になっているかと思います。

またcallback関数をcallする方法として可変関数を用いましたが、
call_user_func_array を用いても同じような実装を出来るかと思います。

まとめ

Web領域よりは低レイヤー領域の方が大好きなので、
どうしても何気ない実装から低レイヤーを思い返して脳トレしたくなります笑

2021年もよろしくお願いします!

Discussion