Given an array, I would like a flattened version of the array keys. Each array key would need the 'path' of the array, to that point, appended with an underscore.
An example explains this best.
$arr = array("location"=>0,"details"=>array("width"=>0,"height"=>0,"level"=>array("three"=>0)));
function answer($arr) {....}
the answer function would return this:
array("location","details_width","details_height","details_level_three");
UPDATE:
Here is the work in progress. It will accept an array and return the array keys, but with no depth:
function recursive_keys($input)
{
$output = array_keys($input);
foreach($input as $sub){
if(is_array($sub)){
$output = array_merge($output, recursive_keys($sub));
}
}
return $output;
}
$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($arr));
$results = array();
foreach ($ritit as $leafValue) {
$path = array();
foreach (range(0, $ritit->getDepth()) as $depth) {
$path[] = $ritit->getSubIterator($depth)->key();
}
$results[] = join('_', $path);
}
function recursive_keys(array $array, array $path = array()) {
$result = array();
foreach ($array as $key => $val) {
$currentPath = array_merge($path, array($key));
if (is_array($val)) {
$result = array_merge($result, recursive_keys($val, $currentPath));
} else {
$result[] = join('_', $currentPath);
}
}
return $result;
}
Demo here: http://codepad.viper-7.com/WQ3UYI
Related
Is there a simple way to take a single-dimensional array and convert it to a multi-dimensional array based on the spaces, or any character(s), in the keys?
$arr['foo1'] = 'bar1';
$arr['foo2'] = 'bar2';
$arr['foo3 tier1' ] = 'bar3';
$arr['foo4 tier1' ] = 'bar4';
and turn it into
$arr['foo1'] = 'bar1';
$arr['foo2'] = 'bar2';
$arr['foo3']['tier1'] = 'bar3';
$arr['foo4']['tier1'] = 'bar4';
you can always do some sort of foreach loop
$newarr = array();
foreach ($arr as $key => $value) {
$output = explode(' ',$key,2);
if(count($output)) {
$newarr[$output[0]][$output[1]] = $value;
} else {
$newarr[$key] = $value;
}
}
That code will only work for one space but you can expand it to multiple spaces, something like
function pivot($arr,$delimeter) {
$return = array();
foreach ($arr as $key => $value) {
$output = explode($delimeter,$key,2);
if(count($output)) {
if(strpos($output[1],$delimeter) > 0) {
$return[$output[0]] = pivot(array($output[1]=>$value),$delimeter);
} else {
$return[$output[0]][$output[1]] = $value;
}
} else {
$return[$key] = $value;
}
}
return $return;
}
I have an array wich is structured like this
foo = stuff we don't care for this example
foo1_value
foo1_label
foo1_unit
foo2_value
foo3_label
foo3_value
Can you figure out a fast way to make it look like that ?
foo
foo1
value
label
unit
foo2
value
foo3
value
label
I'm actually trying with something like this :
array_walk($array, function($val, $key) use(&$nice_array) {
$match = false;
preg_match("/_label|_value|_unit|_libelle/", $key, $match);
if (count($match)) {
list($name, $subName) = explode('_', $key);
$nice_array[$name][$subName] = $val;
} else {
$nice_array[$key] = $val;
}
});
echo '<pre>';
print_r($nice_array);
echo '</pre>';
This is working I'll just have to reflect on the foo_foo_label thing and it's all good
You could use explode on the array keys, something like this:
$newArray = array();
foreach ( $array as $key => $value )
{
$parts = explode('_', $key);
$newArray[$parts[0]][$parts[1]] = $value;
}
Edit: update as detailed in comments. Will handle your foo_foo_value case as well as foo and foo_foo. There's really no reason to use array_walk if you're only passing the results off to a second array.
$newArray = array();
foreach ( $array as $key => $value ) {
if ( preg_match('/_(label|value|unit)$/', $key) === 0 ) {
$newArray[$key] = $value;
continue;
}
$pos = strrpos($key, '_');
$newArray[substr($key, 0, $pos)][substr($key, $pos+1, strlen($key))] = $value;
}
What you can do is loop over the array, and split (explode()) each key on _ to build your new array.
$newArray = array();
foreach($oldArray as $key=>$value){
list($name, $subName) = explode('_', $key);
if($subName !== NULL){
if(!isset($newArray[$name])){
$newArray[$name] = array();
}
$newArray[$name][$subName] = $value;
}
else{
$newArray[$name] = $value;
}
}
$nice_array = array();
array_walk($array, function($val, $key) use(&$nice_array) {
$match = false;
preg_match("/_label|_value|_unit|_libelle/", $key, $match);
if (count($match)) {
$tname = preg_split("/_label$|_value$|_unit$|_libelle$/",$key);
$name = $tname[0];
$subName = substr($match[0],1);
$nice_array[$name][$subName] = $val;
} else {
$nice_array[$key] = $val;
}
});
Given a multidimensional array or dictionary $array.
And assuming that $array['foo']['bar']['baz'] = 'something';
Is there a way other than via an eval statement for me use the multi-dimentional index foo/bar/baz? (The use case is in creating the index dynamically i.e. The function does not know what /foo/bar/baz/ is).
The only way I could figure to do this was:
$item = testGetIndex($array, "'foo']['bar']['baz'");
function testGetIndex($array, $index) {
eval('$item = $array[' . $index . '];');
return $item;
}
Note:
I should mention that I do not want to search this array. This is a weird use case. I am being passed a very large multi dimensional array and it's ugly to have to use constructs like..
$array[foo][bar]..[baz] to make modifications to the array.
Blatently reusing my answer here:
function recurseKeys(array $keys,array $array){
$key = array_shift($keys);
if(!isset($array[$key])) return null;
return empty($keys) ?
$array[$key]:
recurseKeys($keys,$array[$key];
}
$values = recurseKeys(explode('/','foo/bar/baz'),$yourArray);
edit: as Jack pointed out, recursion is not needed:
function findByKey(array $keys,array $array){
while(!is_null($key = array_shift($keys))){
if(!isset($array[$key])) return null;
$array = $array[$key];
}
return $array;
}
$values = findByKey(explode('/','foo/bar/baz'),$yourArray);
To modify an array using a path:
function setPath(&$root, $path, $value)
{
$paths = explode('/', $path);
$current = &$root;
foreach ($paths as $path) {
if (isset($current[$path])) {
$current = &$current[$path];
} else {
return null;
}
}
return $current = $value;
}
$path = 'foo/bar/baz';
$root = array('foo' => array('bar' => array('baz' => 'something')));
setPath($root, $path, '123');
You can tweak the function to just return a reference to the element you wish to change:
function &getPath(&$root, $path)
{
$paths = explode('/', $path);
$current = &$root;
foreach ($paths as $path) {
if (isset($current[$path])) {
$current = &$current[$path];
} else {
return null;
}
}
return $current;
}
$x = &getPath($root, $path);
$x = 456; // $root['foo']['bar']['baz'] == 456
A simple loop can make it, like:
function get(array $array, $keys) {
$val = $array;
foreach (explode('/', $keys) as $part) {
if (!isset($val[$part])) {
return null;
}
$val = $val[$part];
}
return $val;
}
$array['foo']['bar']['baz'] = 'something';
echo get($array, 'foo/bar/baz');
http://ideone.com/vcRvXW
Edit:
For modification, just use references:
function set(array &$array, $keys, $value) {
$val = &$array;
foreach (explode('/', $keys) as $part) {
if (!isset($val[$part])) {
$val[$part] = array();
}
$val = &$val[$part];
}
$val = $value;
}
http://ideone.com/WUNhF6
What is the best way to flatten an array with multiple leaf nodes so that each full path to leaf is a distinct return?
array("Object"=>array("Properties"=>array(1, 2)));
to yield
Object.Properties.1
Object.Properties.2
I'm able to flatten to Object.Properties.1 but 2 does not get processed with recursive function:
function flattenArray($prefix, $array)
{
$result = array();
foreach ($array as $key => $value)
{
if (is_array($value))
$result = array_merge($result, flattenArray($prefix . $key . '.', $value));
else
$result[$prefix . $key] = $value;
}
return $result;
}
I presume top down will not work when anticipating multiple leaf nodes, so either need some type of bottom up processing or a way to copy array for each leaf and process (althought that seems completely inefficient)
function flatten(array $data, $separator = '.') {
$result = array();
$stack = array();
$path = null;
reset($data);
while (!empty($data)) {
$key = key($data);
$element = $data[$key];
unset($data[$key]);
if (is_array($element)) {
if (!empty($data)) {
$stack[] = array($data, $path);
}
$data = $element;
$path .= $key . $separator;
} else {
$result[$path . $key] = $element;
}
if (empty($data) && !empty($stack)) {
list($data, $path) = array_pop($stack);
}
}
return $result;
}
var_dump(flatten(array("Object"=>array("Properties"=>array(1, 2)))));
Output:
array(2) {
["Object.Properties.0"]=>
int(1)
["Object.Properties.1"]=>
int(2)
}
Use function flatMapAssoc() from Kdyby Framework:
$flattened= array();
flatMapAssoc($array, function ($value, $keys) use (&$flattened) {
$flattened[implode('.', $keys)] = $value;
});
/**
* #param array|\Traversable $array
* #param callable $callback
* #return array
*/
function flatMapAssoc($array, $callback)
{
$callback = callback($callback);
$result = array();
$walker = function ($array, $keys = array()) use (&$walker, &$result, $callback) {
foreach ($array as $key => $value) {
$currentKeys = $keys + array(count($keys) => $key);
if (is_array($value)) {
$walker($value, $currentKeys);
continue;
}
$result[] = $callback($value, $currentKeys);
}
return $result;
};
return $walker($array);
}
I would use a wrapper function to hide implementation details (the prefix parameter)
and added an if branch to test for empty arrays. At last, in case of simple leaf you should use the $value variable and not the $key one.
$x = array("Object"=>array("Properties"=>array(1, 2), "test"=>array(), "post"));
function flatten ($array) {
return flattenArray('',$array);
}
function flattenArray($prefix, $array) {
$result = array();
foreach ($array as $key => $value) {
if (is_array($value)) {
if(count($value)) {
$result = array_merge($result, flattenArray($prefix."$key.", $value));
} else {
$result[] = "$prefix$key";
}
} else {
$result[] = "$prefix$value";
}
}
return $result;
}
echo join("\n", flatten($x));
If you want to mimic a tree structure, maybe you can use a different array structure. Something like this:
$y = array ("Object",
array("Properties", 1, 2),
"test",
"post"
);
and flattenArray becomes:
function flattenArray($prefix, $array) {
$result = array();
$prefix .=array_shift($array).'.';
foreach ($array as $value) {
if (is_array($value)) {
$result = array_merge($result, flattenArray($prefix, $value));
} else {
$result[] = "$prefix$value";
}
}
return $result;
}
From:
$arr = array(array('key1'=>'A',...),array('key1'=>'B',...));
to:
array('A','B',..);
$output = array();
foreach ($arr as $array_piece) {
$output = array_merge($output, $array_piece);
}
return array_values($output);
On the other hand, if you want the first value from each array, what you want is...
$output = array();
foreach ($arr as $array_piece) {
$output[] = array_unshift($array_piece);
}
But I'm thinking you want the first one.
Relatively simple conversion by looping:
$newArray = array()
foreach ($arr as $a) {
foreach ($a as $key => $value) {
$newArray[] = $value;
}
}
Or, perhaps more elegantly:
function flatten($concatenation, $subArray) {
return array_merge($concatenation, array_values($subArray));
}
$newArray = array_reduce($arr, "flatten", array());
John's solution is also nice.
Something like this should work
<?
$arr = array(array('key1'=>'A','key2'=>'B'),array('key1'=>'C','key2'=>'D'));
$new_array = array();
foreach ($arr as $key => $value) {
$new_array = array_merge($new_array, array_values($value));
}
var_export($new_array);
?>
If you want all the values in each array inside your main array.
function collapse($input) {
$buf = array();
if(is_array($input)) {
foreach($input as $i) $buf = array_merge($buf, collapse($i));
}
else $buf[] = $input;
return $buf;
}
Above is a modified unsplat function, which could also be used:
function unsplat($input, $delim="\t") {
$buf = array();
if(is_array($input)) {
foreach($input as $i) $buf[] = unsplat($i, $delim);
}
else $buf[] = $input;
return implode($delim, $buf);
}
$newarray = explode("\0", unsplat($oldarray, "\0"));