2010年04月10日

php:関数を再帰的に利用できる関数

配列に対して関数を再帰的に適用させる関数は幾つかありますが、どれも使い勝手が悪い。array_walk_recursive では与え値は1つだけで、コールバックに渡されるのは「配列の値、配列の添字、与え値」と言う感じで、組み込み関数にも適用しづらい。コールバックには「処理対象、オプション1、オプション2・・・」って渡す方が使いやすい。

recursive('mb_convert_encoding', $ary, 'UTF-8', 'SJIS');
recursive('trim', $ary);

とか出来ればラクだよなー。
なので与え値可変長で、関数を再帰的に利用する関数を作ってみました。

ちなみに array_map_recursive も巷で見かけるけど array_map 自体が配列の処理が前提で、与え値にも配列を要求するので再帰処理として面倒(じゃない?)
他、使えそうなのって無かったよなぁ・・・。探す前に作ってしまうのは、仕事ってより骨休めの趣味なんだろなぁ。

「結果の違い」で結果の表記を間違っていましたので直しました。(同日修正)


 ソース

※ ヘボい処理をシェイプアップにしました(2010/11/11)
※ php4 は __FUNCTION__ を 'recursive'(関数名)にした方が無難。
function recursive($fnc, $ary){
  $arg = func_get_args();
  foreach($ary as $key => $val){
    $arg[1] = $val;
    if(is_array($val)){
      $ary[$key] = call_user_func_array(__FUNCTION__, $arg);
    }else{
      $ary[$key] = call_user_func_array($fnc, array_slice($arg, 1));
    }
  }
  return $ary;
}

function recursive($fnc, $ary){
  $arg = array_slice(func_get_args(), 2);
  $new = array();
  foreach($ary as $key => $val){
    if(is_array($val)){
      $tmp = array_merge(array($fnc), array($val), $arg);
      $new[$key] = call_user_func_array('recursive', $tmp);
    }else{
      $tmp = array_merge(array($val), $arg);
      $new[$key] = call_user_func_array($fnc, $tmp);
    }
  }
  return $new;
}


■ 使い方
recursive('mb_convert_encoding', $ary, 'UTF-8', 'SJIS');
recursive('trim', $ary);

配列を与えると再帰的に処理された配列が返される。
与え値の数は最低2つ。

■ 自前の環境
PHP Version 5.2.3
System Windows NT
Server API Apache 2.0
call_user_func_array が PHP 4.0.4 から、それ以外は 4 からなので PHP 4.0.4 から動くかな。(未確認)

ちなみに func_get_args が関数内にあります。下記が面白いので参考下さい。
PHPのfunc_get_argsの謎
http://ido.nu/kuma/2007/07/03/php-wonders-func_get_args/
func_get_args(): Can't be used as ・・・ って怒られたら外に出して下さいw

 ついでに array_walk_recursive も使いやすくしてみる

function walk(&$a, $b, $c = array()){
  if(!is_array($a)){
    $a = call_user_func_array(current(array_splice($c, 0, 1, $a)), $c);
  }else{
    $args = array_slice(func_get_args(), 1);
    return array_walk_recursive($a, 'walk', $args);
  }
}

※ array_walk_recursive は PHP 5 からです。

実質的には recursive() と同じ結果が得られます。違いは・・・。
・ 与え値は array_walk_recursive の順序に準ずる
・ array_walk_recursive 同様に参照渡しになる
・ 処理された配列は array_walk_recursive 同様に参照を含む
・ 戻り値は array_walk_recursive に準ずる

■ 使い方
walk($ary, 'mb_convert_encoding', 'UTF-8', 'SJIS');
walk($ary, 'trim');
配列を与えると再帰的に処理された配列が返される。
当然、結果は $ary に直接入る。

■ 結果の違い
・ テスト関数
function f($v1, $v2, $v3){return ($v1 * $v2 + $v3);}
・ テスト配列
$ary = array(array(0,1,2), array(array(10, 11, 12), array(20, 21, 22)));

recursive()の場合
var_dump(recursive('f', $ary, 3, 2));
array
  0 =>
    array
      0 => int 2
      1 => int 5
      2 => int 8
  1 =>
    array
      0 =>
        array
          0 => int 32
          1 => int 35
          2 => int 38
      1 =>
        array
          0 => int 62
          1 => int 65
          2 => int 68

walk()の場合
walk($ary, 'f', 3, 2);
var_dump($ary);
array
  0 => &
    array
      0 => int 2
      1 => int 5
      2 => int 8
  1 => &
    array
      0 => &
        array
          0 => int 32
          1 => int 35
          2 => int 38
      1 => &
        array
          0 => int 62
          1 => int 65
          2 => int 68


 さらについでに array_map_recursive の例2種

for を使わない方法
function array_map_recursive($f, $v)
{
  return
  (
    is_array($v) ?
      (
        !empty($v) ?
          array_combine(
            array_keys($v),
            array_map(
              'array_map_recursive',
              array_fill(0, count($v), $f),
              $v
            )
          )
        : $v
      )
    : $f($v)
  );
}

for を使った方法(こちのが速い)
function array_map_recursive($cb, $dat){
  if(is_array($dat)){
    $new = array();
    foreach($dat as $k => $v){
      if(is_array($v)) $new[$k] = array_map_recursive($cb, $v);
      else $new[$k] = call_user_func($cb, $v);
    }
    return $new;
  }else return call_user_func($cb, $dat);
}


とは言え与え値が渡せないので、与え値不要の関数にしか使えない。



posted by HiFa at 14:23 | 🌁 | Comment(0) | TrackBack(0) | ときどきPHP | このブログの読者になる | 更新情報をチェックする
>>> スパムコメントは消してますよん。 お互い無駄な労力は避けましょう。 <<<

この記事へのコメント

コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック