I have a multidimensional array like this one in PHP:
Array
(
[folder1] => Array
(
[folder11] => Array
(
[0] => index.html
[1] => tester.html
)
[folder12] => Array
(
[folder21] => Array
(
[0] => astonmartindbs.jpg
)
)
)
)
and should be converted to a "file path" string like this one:
Array
(
[0] => 'folder1/folder11/index.html'
[1] => 'folder1/folder11/tester.html'
[2] => 'folder1/folder12/folder21/astonmartindbs.jpg'
)
Has anybody any ideas?
I have tried a lot any all deleted... This is the starting point of my last try:
public function processArray( $_array ) {
foreach( $_array AS $key => $value ) {
if( is_int( $key ) ) {
} else {
if( is_array( $value ) ) {
$this->processArray( $value );
} else {
}
}
}
echo $this->string;
}
But i do not come to an end.... Hope somebody can help?
A recursive function may be what you are searching for. The following function will work:
/**
* Flattens the array from the question
*
* #param array $a Array or sub array of directory tree
* #param string $prefix Path prefix of $a
*/
function flatten($a, $prefix = './') {
$paths = array();
foreach($a as $index => $item) {
// if item is a string then it is a file name (or a leaf in tree)
// prefix it and add it to paths
if(is_string($item)) {
$paths []= $prefix . $item;
} else {
// if item is a directory we call flatten on it again.
// also we append the new folder name to $prefix
foreach(flatten($item, $prefix . $index . '/') as $path) {
$paths []= $path;
}
}
}
return $paths;
}
var_dump(flatten($a));
Note that flatten() call itself inside the foreach loop with a sub array as argument. This is called a 'recursive algorithm'.
If you like the SPL you can use RecursiveArrayIterator and RecursiveIteratorIterator to iterate over a flat structure.
My result would look like this:
$arr = array(); // your array
$arr = new RecursiveArrayIterator($arr);
$iterator = new RecursiveIteratorIterator($arr, RecursiveIteratorIterator::SELF_FIRST);
$currentDepth = 0;
$currentPath = array();
$result = array();
foreach($iterator as $key => $value) {
// if depth is decreased
if ($iterator->getDepth() < $currentDepth) {
// pop out path values
do {
$currentDepth--;
array_pop($currentPath);
} while($iterator->getDepth() < $currentDepth);
}
if (is_array($value)) {
// add parent to the path
$currentPath[] = $key;
$currentDepth++;
} else {
// add children to result array
$result[] = implode('/', $currentPath).'/'.$value;
}
}
Dumping the data would then look like this:
print_r($result);
/*
Array
(
[0] => folder1/folder11/index.html
[1] => folder1/folder11/tester.html
[2] => folder1/folder12/folder21/astonmartindbs.jpg
)
*/
In your case, you need to implement, a recursive function, that you tried to do, here is a simple code, it may help you,
i am not sure if that is working or no:
$result = array();
$d = 0;
$tmp = "";
public function processArray( $_array ,$before) {
foreach( $_array AS $key => $value ) {
if( is_int( $key ) ) { // If the key is a number, then there is no a sub-array
$result[$d] = $before . '/' . $value;
$d++;
$before="";
} else {
if( is_array( $value ) ) { // if the value is an array, then we will add the key into string that we will return and search into subarray.
$before = $before . '/' . $key;
$this->processArray( $value,$before );
} else {
}
}
}
return $result;
}
Related
I have an array that looks like this:
$array = array (
[level_1] => array (
[level_2] => array (
[level_3] => something
)
),
[level_12] => array (
[level_2] => somethingelse
),
[level_13] => array (
[level_22] => array (
[level_3] => something
)
),
);
The keys or values aren't always unique but the branches are.
And I have a string that looks like this:
$string = 'level_1-level_2-level_3';
Those are the keys for a branch.
And I need to somehow get the value from the array based on that string?
Like this:
$string_array = explode('-', $string);
$array[$string_array[0]][$string_array[1]][$string_array[2]] // something
But since the depth can be different this is not a viable solution...
Try this simple example, no need for a recursive function:
function get_item( $path, $array )
{
$paths = explode( '-', $path );
$result = $array;
foreach ( $paths as $path) {
isset( $result[$path] ) ? $result = $result[$path] : $result = false;
}
return $result;
}
$path = 'level_1-level_2-level_3';
echo get_item( $path, $array );
Try this:
$array = array (
'level_1' => array (
'level_2' => array (
'level_3' => 'something'
)
),
'level_12' => array (
'level_2' => 'somethingelse'
),
'level_13' => array (
'level_22' => array (
'level_3' => 'something'
)
),
);
$string = 'level_1-level_2-level_3';
$keys = explode('-', $string);
echo getItemIterative($keys, $array);
echo "\n";
echo getItemRecursive($keys, $array);
function getItemIterative($keys, $array)
{
$value = null;
foreach ($keys as $key) {
if ($value == null) {
$value = $array[$key];
}
if (is_array($value) && array_key_exists($key, $value)) {
$value = $value[$key];
}
}
return $value;
}
function getItemRecursive($keys, $array)
{
$key = array_shift($keys);
$value = $array[$key];
if (empty($keys)) {
return $value;
} else {
return getItemRecursive($keys, $value);
}
}
Make a $result variable which initially points to the root of the array, and loop through the levels of your $string_array 'til $result points at the leaf you were looking for.
// stuff you already have:
$array = array(...); // your big array
$string = 'level_1-level_2-level_3';
$string_array = explode('-', $string);
// new stuff:
$result = $array;
foreach ($string_array as $level) {
$result = $result[$level];
}
echo $result; // 'something'
Working example: Ideone
how can I add an extra dimesion before each element in N-dimensional array (recursively)? Let's say I have
Array
(
[room] => Array
(
[bed] = Array
(
[material] => wood
)
)
)
And I want to add an extra "[0]" dimension before room, bed and material. (Adding dimension only if the last element is an array). I also want to distinguish, if there already is the extra [0] dimension, so it will not appear twice .. + I don't want to add [0] if the array key is named "#attribute".
I'm trying to figure it out, but I'm really lost. This is what I've got so far..
function normalize_array (&$array) {
if (is_array($array)) {
if (!isset($array[0])) {
$array = array ( "0" => $array);
}
foreach ($array[0] as $next) {
normalize_array ($next);
}
}
}
but it does not work recursively. Any help will be appreciated. Thanks!
From the documentation of foreach:
In order to be able to directly modify array elements within the loop
precede $value with &. In that case the value will be assigned by reference.
So if you modify your function like this, it works:
function normalize_array (&$array) {
if (is_array($array)) {
if (!isset($array[0])) {
$array = array ( "0" => $array);
}
foreach ($array[0] as &$next) { // <-- add & here
normalize_array ($next);
}
}
}
Possible solution (tested just a little, but seems to work):
function normalize_array($array, $keys){
$new = array();
foreach($array as $key => $value){
if (in_array($key, $keys) && is_array($value)){
$new["0"] = array($key => normalize_array($value, $keys));
} else {
$new[$key] = $value ;
}
}
return $new ;
}
$data = array(
"room" => array(
"bed" => array(
"material" => "wood"
)
)
);
$keys = array("room", "bed", "material");
$new = normalize_array($data, $keys);
var_dump($new);
Final solution:
function normalize_array_rec (&$array) {
if (is_array($array)) {
if (!isset($array[0])) {
$array = array ( "0" => $array);
}
$i = 0;
while (isset($array[$i])) {
foreach ($array[$i] as &$next) {
normalize_array_rec ($next);
}
$i++;
}
}
}
Forgot to call function in each instance of array, not only in [0] index.
I have the next INI file:
a.b.c = 1
a.b.d.e = 2
I am parsing this file using parse_ini_file. And it returns:
array(
'a.b.c' => 1,
'a.b.d.e' => 2
)
But I want to create a multidimensional array. My outout should be:
array(
'a' => array(
'b' => array(
'c' => 1,
'd' => array(
'e' => 2
)
)
)
)
Thank you in advance.
This is how I see it:
<?php
class ParseIniMulti {
public static function parse($filename) {
$ini_arr = parse_ini_file($filename);
if ($ini_arr === FALSE) {
return FALSE;
}
self::fix_ini_multi(&$ini_arr);
return $ini_arr;
}
private static function fix_ini_multi(&$ini_arr) {
foreach ($ini_arr AS $key => &$value) {
if (is_array($value)) {
self::fix_ini_multi($value);
}
if (strpos($key, '.') !== FALSE) {
$key_arr = explode('.', $key);
$last_key = array_pop($key_arr);
$cur_elem = &$ini_arr;
foreach ($key_arr AS $key_step) {
if (!isset($cur_elem[$key_step])) {
$cur_elem[$key_step] = array();
}
$cur_elem = &$cur_elem[$key_step];
}
$cur_elem[$last_key] = $value;
unset($ini_arr[$key]);
}
}
}
}
var_dump(ParseIniMulti::parse('test.ini'));
It's actually quite simple, you only need to change the format of the array you already have by exploding it's key:
$ini_preparsed = array(
'a.b.c' => 1,
'a.b.d.e' => 2
);
$ini = array();
foreach($ini_preparsed as $key => $value)
{
$p = &$ini;
foreach(explode('.', $key) as $k)
$p = &$p[$k];
$p = $value;
}
unset($p);
print_r($ini);
Output:
Array
(
[a] => Array
(
[b] => Array
(
[c] => 1
[d] => Array
(
[e] => 2
)
)
)
)
See as well: String with array structure to Array.
Have a look at the Zend_Config_Ini class. It does what you want, you can use it standalone (without the rest of Zend Framework) and as a bonus it supports section inheritance.
With the toArray method you can create an array from the config object.
Take a look at PHProp.
Similar to Zend_Config_Ini, but you can refer to a key in your config like ${key}
It's a my class for parsing config ini files to a multidimensional array:
class Cubique_Config {
const SEPARATOR = '.';
private static $_data = null;
public static function get() {
if (is_null(self::$_data)) {
$commonIniFile = APP . '/config' . '/common.ini';
$envIniFile = APP . '/config' . '/' . ENV . '.ini';
if (!file_exists($commonIniFile)) {
throw new Exception('\'' . $commonIniFile . '\' config file not found');
}
if (!file_exists($envIniFile)) {
throw new Exception('\'' . $envIniFile . '\' config file not found');
}
$commonIni = parse_ini_file($commonIniFile);
$envIni = parse_ini_file($envIniFile);
$mergedIni = array_merge($commonIni, $envIni);
self::$_data = array();
foreach ($mergedIni as $rowKey => $rowValue) {
$explodedRow = explode(self::SEPARATOR, $rowKey);
self::$_data = array_merge_recursive(self::$_data, self::_subArray($explodedRow, $rowValue));
}
}
return self::$_data;
}
private static function _subArray($explodedRow, $value) {
$result = null;
$explodedRow = array_values($explodedRow);
if (count($explodedRow)) {
$firstItem = $explodedRow[0];
unset($explodedRow[0]);
$result[$firstItem] = self::_subArray($explodedRow, $value);
} else {
$result = $value;
}
return $result;
}
}
I have the next INI file:
a.b.c = 1
a.b.d.e = 2
I am parsing this file using parse_ini_file. And it returns:
array(
'a.b.c' => 1,
'a.b.d.e' => 2
)
But I want to create a multidimensional array. My outout should be:
array(
'a' => array(
'b' => array(
'c' => 1,
'd' => array(
'e' => 2
)
)
)
)
Thank you in advance.
This is how I see it:
<?php
class ParseIniMulti {
public static function parse($filename) {
$ini_arr = parse_ini_file($filename);
if ($ini_arr === FALSE) {
return FALSE;
}
self::fix_ini_multi(&$ini_arr);
return $ini_arr;
}
private static function fix_ini_multi(&$ini_arr) {
foreach ($ini_arr AS $key => &$value) {
if (is_array($value)) {
self::fix_ini_multi($value);
}
if (strpos($key, '.') !== FALSE) {
$key_arr = explode('.', $key);
$last_key = array_pop($key_arr);
$cur_elem = &$ini_arr;
foreach ($key_arr AS $key_step) {
if (!isset($cur_elem[$key_step])) {
$cur_elem[$key_step] = array();
}
$cur_elem = &$cur_elem[$key_step];
}
$cur_elem[$last_key] = $value;
unset($ini_arr[$key]);
}
}
}
}
var_dump(ParseIniMulti::parse('test.ini'));
It's actually quite simple, you only need to change the format of the array you already have by exploding it's key:
$ini_preparsed = array(
'a.b.c' => 1,
'a.b.d.e' => 2
);
$ini = array();
foreach($ini_preparsed as $key => $value)
{
$p = &$ini;
foreach(explode('.', $key) as $k)
$p = &$p[$k];
$p = $value;
}
unset($p);
print_r($ini);
Output:
Array
(
[a] => Array
(
[b] => Array
(
[c] => 1
[d] => Array
(
[e] => 2
)
)
)
)
See as well: String with array structure to Array.
Have a look at the Zend_Config_Ini class. It does what you want, you can use it standalone (without the rest of Zend Framework) and as a bonus it supports section inheritance.
With the toArray method you can create an array from the config object.
Take a look at PHProp.
Similar to Zend_Config_Ini, but you can refer to a key in your config like ${key}
It's a my class for parsing config ini files to a multidimensional array:
class Cubique_Config {
const SEPARATOR = '.';
private static $_data = null;
public static function get() {
if (is_null(self::$_data)) {
$commonIniFile = APP . '/config' . '/common.ini';
$envIniFile = APP . '/config' . '/' . ENV . '.ini';
if (!file_exists($commonIniFile)) {
throw new Exception('\'' . $commonIniFile . '\' config file not found');
}
if (!file_exists($envIniFile)) {
throw new Exception('\'' . $envIniFile . '\' config file not found');
}
$commonIni = parse_ini_file($commonIniFile);
$envIni = parse_ini_file($envIniFile);
$mergedIni = array_merge($commonIni, $envIni);
self::$_data = array();
foreach ($mergedIni as $rowKey => $rowValue) {
$explodedRow = explode(self::SEPARATOR, $rowKey);
self::$_data = array_merge_recursive(self::$_data, self::_subArray($explodedRow, $rowValue));
}
}
return self::$_data;
}
private static function _subArray($explodedRow, $value) {
$result = null;
$explodedRow = array_values($explodedRow);
if (count($explodedRow)) {
$firstItem = $explodedRow[0];
unset($explodedRow[0]);
$result[$firstItem] = self::_subArray($explodedRow, $value);
} else {
$result = $value;
}
return $result;
}
}
Suppose I have an array in PHP that looks like this
array
(
array(0)
(
array(0)
(
.
.
.
)
.
.
array(10)
(
..
)
)
.
.
.
array(n)
(
array(0)
(
)
)
)
And I need all the leaf elements of this mulit-dimensional array into a linear array, how should I go about doing this without resorting recursion, such like this?
function getChild($element)
{
foreach($element as $e)
{
if (is_array($e)
{
getChild($e);
}
}
}
Note: code snippet above, horribly incompleted
Update: example of array
Array
(
[0] => Array
(
[0] => Array
(
[0] => Seller Object
(
[credits:private] => 5000000
[balance:private] => 4998970
[queueid:private] => 0
[sellerid:private] => 2
[dateTime:private] => 2009-07-25 17:53:10
)
)
)
...snipped.
[2] => Array
(
[0] => Array
(
[0] => Seller Object
(
[credits:private] => 10000000
[balance:private] => 9997940
[queueid:private] => 135
[sellerid:private] => 234
[dateTime:private] => 2009-07-14 23:36:00
)
)
....snipped....
)
)
Actually, there is a single function that will do the trick, check the manual page at: http://php.net/manual/en/function.array-walk-recursive.php
Quick snippet adapted from the page:
$data = array('test' => array('deeper' => array('last' => 'foo'), 'bar'), 'baz');
var_dump($data);
function printValue($value, $key, $userData)
{
//echo "$value\n";
$userData[] = $value;
}
$result = new ArrayObject();
array_walk_recursive($data, 'printValue', $result);
var_dump($result);
You could use iterators, for example:
$result = array();
foreach(new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::LEAVES_ONLY) as $value) {
$result[] = $value;
}
Use a stack:
<?php
$data = array(array(array("foo"),"bar"),"baz");
$results = array();
$process = $data;
while (count($process) > 0) {
$current = array_pop($process);
if (is_array($current)) {
// Using a loop for clarity. You could use array_merge() here.
foreach ($current as $item) {
// As an optimization you could add "flat" items directly to the results array here.
array_push($process, $item);
}
} else {
array_push($results, $current);
}
}
print_r($results);
Output:
Array
(
[0] => baz
[1] => bar
[2] => foo
)
This should be more memory efficient than the recursive approach. Despite the fact that we do a lot of array manipulation here, PHP has copy-on-write semantics so the actual zvals of the real data won't be duplicated in memory.
Try this:
function getLeafs($element) {
$leafs = array();
foreach ($element as $e) {
if (is_array($e)) {
$leafs = array_merge($leafs, getLeafs($e));
} else {
$leafs[] = $e;
}
}
return $leafs;
}
Edit Apparently you don’t want a recursive solution. So here’s an iterative solution that uses a stack:
function getLeafs($element) {
$stack = array($element);
$leafs = array();
while ($item = array_pop($stack)) {
while ($e = array_shift($item)) {
if (is_array($e)) {
array_push($stack, array($item));
array_push($stack, $e);
break;
} else {
$leafs[] = $e;
}
}
}
return $leafs;
}
Just got the same issue and used another method that was not mentioned. The accepted answer require the ArrayObject class to work properly. It can be done with the array primitive and the use keyword in the anonymous function (PHP >= 5.3):
<?php
$data = array(
array(1,2,3,4,5),
array(6,7,8,9,0),
);
$result = array();
array_walk_recursive($data, function($v) use (&$result) { # by reference
$result[] = $v;
});
var_dump($result);
There is no flatten function to get directly the leafs. You have to use recursion to check for each array if has more array children and only when you get to the bottom to move the element to a result flat array.