[php] 多次元配列を平坦化する方法


Answers

PHP 5.3以降 、最短の解決策は、新しいクロージャの構文を持つarray_walk_recursive()ようarray_walk_recursive()

function flatten(array $array) {
    $return = array();
    array_walk_recursive($array, function($a) use (&$return) { $return[] = $a; });
    return $return;
}
Question

再帰や参照を使用せずに(双対/多)次元配列を平坦化することはPHPで可能ですか?

私はキーに無視できるので、私はarray_map()array_values()行を考えているので、値にのみ興味があります。




PHPの多次元配列をHTML入力形式で表現する必要がありました。

$test = [
    'a' => [
        'b' => [
            'c' => ['a', 'b']
        ]
    ],
    'b' => 'c',
    'c' => [
        'd' => 'e'
    ]
];

$flatten = function ($input, $parent = []) use (&$flatten) {
    $return = [];

    foreach ($input as $k => $v) {
        if (is_array($v)) {
            $return = array_merge($return, $flatten($v, array_merge($parent, [$k])));
        } else {
            if ($parent) {
                $key = implode('][', $parent) . '][' . $k . ']';

                if (substr_count($key, ']') != substr_count($key, '[')) {
                    $key = preg_replace('/\]/', '', $key, 1);
                }
            } else {
                $key = $k;
            }           

            $return[$key] = $v;
        }
    }

    return $return;
};

die(var_dump( $flatten($test) ));

array(4) {
  ["a[b][c][0]"]=>
  string(1) "a"
  ["a[b][c][1]"]=>
  string(1) "b"
  ["b"]=>
  string(1) "c"
  ["c[d]"]=>
  string(1) "e"
}



w / o再帰を(あなたが要求したように)フラット化するには、 stackを使用することができstack 。 当然これをarray_flattenのような独自の関数に入れることができます。 以下はw / oキーで動作するバージョンです:

function array_flatten(array $array)
{
    $flat = array(); // initialize return array
    $stack = array_values($array); // initialize stack
    while($stack) // process stack until done
    {
        $value = array_shift($stack);
        if (is_array($value)) // a value to further process
        {
            $stack = array_merge(array_values($value), $stack);
        }
        else // a value to take
        {
           $flat[] = $value;
        }
    }
    return $flat;
}

要素はその順序で処理されます。 下位要素はスタックの上に移動されるため、次に処理されます。

キーも考慮に入れることは可能ですが、スタックを処理するには別の戦略が必要です。 これは、サブアレイ内の可能な重複キーに対処する必要があるために必要です。 関連する質問と同様の答え: PHPキーを保存しながら多次元配列を歩く

私は確かではありませんが、私は過去にこれをテストしました: RecurisiveIteratorは再帰を使用するので、本当に必要なものに依存します。 スタックに基づいて再帰イテレータを作成することも可能です。

foreach(new FlatRecursiveArrayIterator($array) as $key => $value)
{
    echo "** ($key) $value\n";
}

Demo

私は良い考えであると思うRecursiveIterator基づいてスタックを実装するために、これまでこれを行っていませんでした。




再帰や参照を使用せずに(双対/多)次元配列を平坦化することはPHPで可能ですか?

PHP 7では、私が知っているものではありません。 私は、多次元配列を平坦化および重複排除するために参照と再帰の両方を使用し、多次元配列の再構築のために見つかった各重複の最深ノードの深さを保存するarray_moonwalkというソリューションを作成しました。

foreach 使用することなく 、深さの異なる葉ノードの重複排除を処理します。これまでのところ、 array_walkを利用する唯一の答えarray_walk 。 パフォーマンスについてはテストされていません。 YMMV。




<?php
//recursive solution

//test array
$nested_array = [[1,2,[3]],4,[5],[[[6,[7=>[7,8,9,10]]]]]];

/*-----------------------------------------
function call and return result to an array
------------------------------------------*/
$index_count = 1;
$flatered_array = array();
$flatered_array = flat_array($nested_array, $index_count);

/*-----------------------------------------
Print Result
-----------------------------------------*/
echo "<pre>";
print_r($flatered_array);


/*-----------------------------------------
function to flaten an array 
-----------------------------------------*/
function flat_array($nested_array, & $index_count, & $flatered_array) {

  foreach($nested_array AS $key=>$val) {
      if(is_array($val)) {
        flat_array($val, $index_count, $flatered_array);
      }
      else {
        $flatered_array[$index_count] = $val;
        ++$index_count;
      }      
  }

return $flatered_array;
}
?>



あなたが本当に再帰が好きでないなら...代わりにシフトしよう:)

$a = array(1,2,array(3,4, array(5,6,7), 8), 9);
$o = [];
for ($i=0; $i<count($a); $i++) {
    if (is_array($a[$i])) {
        array_splice($a, $i+1, 0, $a[$i]);
    } else {
        $o[] = $a[$i];
    }
}

注意:この単純なバージョンでは、これは配列キーをサポートしていません。




このバージョンでは深く、浅い、または特定のレベルのレベルを実行できます。

/**
 * @param  array|object $array  array of mixed values to flatten
 * @param  int|boolean  $level  0:deep, 1:shallow, 2:2 levels, 3...
 * @return array
 */
function flatten($array, $level = 0) {
    $level = (int) $level;
    $result = array();
    foreach ($array as $i => $v) {
        if (0 <= $level && is_array($v)) {
            $v = flatten($v, $level > 1 ? $level - 1 : 0 - $level);
            $result = array_merge($result, $v);
        } elseif (is_int($i)) {
            $result[] = $v;
        } else {
            $result[$i] = $v; 
        }
    }
    return $result;
}



ちょうど私はこれが折り畳みであることを指摘していたと思ったので、array_reduceを使うことができます:

array_reduce($my_array, 'array_merge', array());

編集:任意の数のレベルを平坦化するためにこれを構成することができます。 いくつかの方法でこれを行うことができます:

// Reduces one level
$concat   = function($x) { return array_reduce($x, 'array_merge', array()); };

// We can compose $concat with itself $n times, then apply it to $x
// This can overflow the stack for large $n
$compose  = function($f, $g) {
    return function($x) use ($f, $g) { return $f($g($x)); };
};
$identity = function($x) { return $x; };
$flattenA = function($n) use ($compose, $identity, $concat) {
    return  function($x) use ($compose, $identity, $concat, $n) {
        return ($n === 0)? $x
                         : call_user_func(array_reduce(array_fill(0, $n, $concat),
                                                       $compose,
                                                       $identity),
                                          $x);
    };
};

// We can iteratively apply $concat to $x, $n times
$uncurriedFlip     = function($f) {
    return  function($a, $b) use ($f) {
        return $f($b, $a);
    };
};
$iterate  = function($f) use ($uncurriedFlip) {
    return  function($n) use ($uncurriedFlip, $f) {
    return  function($x) use ($uncurriedFlip, $f, $n) {
        return ($n === 0)? $x
                         : array_reduce(array_fill(0, $n, $f),
                                        $uncurriedFlip('call_user_func'),
                                        $x);
    }; };
};
$flattenB = $iterate($concat);

// Example usage:
$apply    = function($f, $x) {
    return $f($x);
};
$curriedFlip = function($f) {
    return  function($a) use ($f) {
    return  function($b) use ($f, $a) {
        return $f($b, $a);
    }; };
};

var_dump(
    array_map(
        call_user_func($curriedFlip($apply),
                       array(array(array('A', 'B', 'C'),
                                   array('D')),
                             array(array(),
                                   array('E')))),
        array($flattenA(2), $flattenB(2))));

もちろん、ループを使うこともできますが、array_mapやarray_valuesの行に沿ってコンビネータ関数を求めます。




/**
 * For merging values of a multidimensional array into one 
 *
 * $array = [
 *     0 => [
 *         0 => 'a1',
 *         1 => 'b1',
 *         2 => 'c1',
 *         3 => 'd1'
 *     ],
 *     1 => [
 *         0 => 'a2',
 *         1 => 'b2',
 *         2 => 'c2',
 *     ]
 * ];
 *
 * becomes : 
 *
 * $array = [
 *     0 => 'a1',
 *     1 => 'b1',
 *     2 => 'c1',
 *     3 => 'd1',
 *     4 => 'a2',
 *     5 => 'b2',
 *     6 => 'c2',
 *     
 * ]
 */
array_reduce
(
    $multiArray
    , function ($lastItem, $currentItem) {
        $lastItem = $lastItem ?: array();
        return array_merge($lastItem, array_values($currentItem));
    }
);

要点スニペット




PHP 5.6以降では、 ... array演算子を使用して外側の配列をアンパックした後、 array_merge 2次元配列を平坦化することができます。 コードは単純明快です。

$a = [[10, 20], [30, 40]];
$b = [["x" => "X", "y" => "Y"], ["p" => "P", "q" => "Q"]];

print_r(array_merge(...$a));
print_r(array_merge(...$b));

Array
(
    [0] => 10
    [1] => 20
    [2] => 30
    [3] => 40
)
Array
(
    [x] => X
    [y] => Y
    [p] => P
    [q] => Q
)

しかし、外側の配列に数字以外のキーがある場合は機能しません。 その場合は、まずarray_valuesを呼び出す必要があります。

$c = ["a" => ["x" => "X", "y" => "Y"], "b" => ["p" => "P", "q" => "Q"]];
print_r(array_merge(...array_values($c)));

Array
(
    [x] => X
    [y] => Y
    [p] => P
    [q] => Q
)



ああ、再帰なし!!! map、reduce、filterは再帰を伴います。 あなたが以下のようないくつかのハッキーなPHPの方法を使用しない限り不可能です。 真剣に、ループは以下のコードに関係しています。申し訳ありませんが、良い配列関数を殺した人は:)

$arr=array(1,2,array(3,4, array(5,6,7), 8), 9);
$json=json_encode($arr);
$hacked_json=str_replace(['[',']'],"",$json);
$hacked_array=json_decode('['.$hacked_json.']');
echo "<pre>";
print_r($hacked_array);

出力

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 1
    [5] => 2
    [6] => 6
    [7] => 7
    [8] => 8
    [9] => 9
)

ネストされたキー値のペアでも機能します

$arr=array(1,2,array(3,4, array(array("s"=>array(1),"w"=>array('q'=>1,'v'=>12)),6,7), 8), 9);
echo "<pre>";
print_r($arr);
$json=json_encode($arr);
$hacked_json=str_replace(['[',']'],"",$json);
$hacked_array=json_decode('['.$hacked_json.']');
echo "<pre>";
print_r(json_decode(json_encode($hacked_array), True));

出力

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => Array
        (
            [s] => 1
            [w] => Array
                (
                    [q] => 1
                    [v] => 12
                )

        )

    [5] => 6
    [6] => 7
    [7] => 8
    [8] => 9
)



あなたはouzo goodiesでそれをすることができます:

 $result = Arrays::flatten($multidimensional);

参照: Here




この解は非再帰的です。 要素の順序はいくぶん混在することに注意してください。

function flatten($array) {
    $return = array();
    while(count($array)) {
        $value = array_shift($array);
        if(is_array($value))
            foreach($value as $sub)
                $array[] = $sub;
        else
            $return[] = $value;
    }
    return $return;
}



Links