Прозрачно сгладить массив

Читая этот вопрос Объединить и сгруппировать по нескольким массивам, я получил следующую идею: при работе с многоуровневыми массивами, с возможными повторяющимися ключами, было бы целесообразно иметь функцию, которая будет перебирать такой массив, как плоский, например

foreach(flatten($deepArray) as $key => $val).... 

любые идеи, как писать flatten() ? Есть ли стандартное решение?

(обратите внимание, что flatten() не может просто вернуть новый массив из-за повторения ключей).

Solutions Collecting From Web of "Прозрачно сгладить массив"

Пример использования RecursiveArrayIterator

 $array = array( 0 => 'a', 1 => array('subA','subB',array(0 => 'subsubA', 1 => 'subsubB', 2 => array(0 => 'deepA', 1 => 'deepB'))), 2 => 'b', 3 => array('subA','subB','subC'), 4 => 'c' ); foreach (return new RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $key => $val) { printf( '%s: %s' . "\n", $key, $val ); } /* Output: 0: a 0: subA 1: subB 0: subsubA 1: subsubB 0: deepA 1: deepB 2: b 0: subA 1: subB 2: subC 4: c */ 

расширение RecursiveIteratorIterator для возврата текущего ключевого стека

 class MyRecursiveIteratorIterator extends RecursiveIteratorIterator { public function key() { return json_encode($this->getKeyStack()); } public function getKeyStack() { $result = array(); for ($depth = 0, $lim = $this->getDepth(); $depth < $lim; $depth += 1) { $result[] = $this->getSubIterator($depth)->key(); } $result[] = parent::key(); return $result; } } foreach ($it = new MyRecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $key => $val) { printf('%s (%s): %s' . "\n", implode('.', $it->getKeyStack()), $key, $val); } /* Output: 0 ([0]): a 1.0 ([1,0]): subA 1.1 ([1,1]): subB 1.2.0 ([1,2,0]): subsubA 1.2.1 ([1,2,1]): subsubB 1.2.2.0 ([1,2,2,0]): deepA 1.2.2.1 ([1,2,2,1]): deepB 2 ([2]): b 3.0 ([3,0]): subA 3.1 ([3,1]): subB 3.2 ([3,2]): subC 4 ([4]): c */ 

Еще одна версия, на этот раз не использующая рекурсивный атрибут:

 function flatten(array $array = array(), $keyStack = array(), $result = array()) { foreach ($array as $key => $value) { $keyStack[] = $key; if (is_array($value)) { $result = flatten($value, $keyStack, $result); } else { $result[] = array( 'keys' => $keyStack, 'value' => $value ); } array_pop($keyStack); } return $result; } foreach (flatten($array) as $element) { printf( '%s: %s (depth: %s)' . "\n", implode('.', $element['keys']), $element['value'], sizeof($element['keys']) ); } /* 0: a (depth: 1) 1.0: subA (depth: 2) 1.1: subB (depth: 2) 1.2.0: subsubA (depth: 3) 1.2.1: subsubB (depth: 3) 1.2.2.0: deepA (depth: 4) 1.2.2.1: deepB (depth: 4) 2: b (depth: 1) 3.0: subA (depth: 2) 3.1: subB (depth: 2) 3.2: subC (depth: 2) 4: c (depth: 1) */ 

Вы также можете написать простую функцию обхода:

 function flatten($node, $fn, $keys = array()) { if (! is_array($node)) { $fn($node, $keys); } else { foreach ($node as $k => $v) { $new_keys = $keys; $new_keys[] = $k; flatten($v, $fn, $new_keys); } } } $array = array( 0 => 'a', 1 => array('subA','subB',array(0 => 'subsubA', 1 => 'subsubB', 2 => array(0 => 'deepA', 1 => 'deepB'))), 2 => 'b', 3 => array('subA','subB','subC'), 4 => 'c' ); // will output: a subA subB subsubA subsubB deepA deepB b subA subB subC c flatten($array, function($v, $k) { echo $v . ' '; }); 

Если вы не хотите вызывать его каждый раз с другой функцией в качестве параметра, я также написал адаптер, который вернет массив:

 function flatten_array($node) { $acc = array(); flatten($node, function($node, $keys) use (&$acc) { $acc[implode('.', $keys)] = $node; }); return $acc; } // will spit out the same output as that in Yoshi's answer: foreach (flatten_array($array) as $k => $v) { echo $k .' => ' . $v . "\n"; } 

Заметки:

  • array_walk_recursive нельзя использовать / не то же самое, поскольку он пропускает ключи, содержащие массив
  • Я написал свои примеры с анонимными функциями; если ваш PHP не является достаточно новым, вы должны назвать функции и вызвать их с помощью call_user_func .