I'm building a small template system and i'm looking for a way to invoke multidimensional associative arrays using dots. For example:
$animals = array(
'four-legged' => array (
'cute' => 'no',
'ugly' => 'no',
'smart' => array('best' => 'dog','worst' => 'willy')
),
'123' => '456',
'abc' => 'def'
);
Then, in my template, if I wanted to show 'dog', I would put:
{a.four-legged.smart.best}
Well, given a string with four-legged.smart.worst:
function getElementFromPath(array $array, $path) {
$parts = explode('.', $path);
$tmp = $array;
foreach ($parts as $part) {
if (!isset($tmp[$part])) {
return ''; //Path is invalid
} else {
$tmp = $tmp[$part];
}
}
return $tmp; //If we reached this far, $tmp has the result of the path
}
So you can call:
$foo = getElementFromPath($array, 'four-legged.smart.worst');
echo $foo; // willy
And if you want to write elements, it's not much harder (you just need to use references, and a few checks to default the values if the path doesn't exist)...:
function setElementFromPath(array &$array, $path, $value) {
$parts = explode('.', $path);
$tmp =& $array;
foreach ($parts as $part) {
if (!isset($tmp[$part]) || !is_array($tmp[$part])) {
$tmp[$part] = array();
}
$tmp =& $tmp[$part];
}
$tmp = $value;
}
Edit: Since this is in a template system, it may be worth while "compiling" the array down to a single dimension once, rather than traversing it each time (for performance reasons)...
function compileWithDots(array $array) {
$newArray = array();
foreach ($array as $key => $value) {
if (is_array($value)) {
$tmpArray = compileWithDots($value);
foreach ($tmpArray as $tmpKey => $tmpValue) {
$newArray[$key . '.' . $tmpKey] = $tmpValue;
}
} else {
$newArray[$key] = $value;
}
}
return $newArray;
}
So that would convert:
$animals = array(
'four-legged' => array (
'cute' => 'no',
'ugly' => 'no',
'smart' => array(
'best' => 'dog',
'worst' => 'willy'
)
),
'123' => '456',
'abc' => 'def'
);
Into
array(
'four-legged.cute' => 'no',
'four-legged.ugly' => 'no',
'four-legged.smart.best' => 'dog',
'four-legged.smart.worst' => 'willy',
'123' => '456',
'abc' => 'def',
);
Then your lookup just becomes $value = isset($compiledArray[$path]) ? $compiledArray[$path] : ''; instead of $value = getElementFromPath($array, $path);
It trades pre-computing for inline speed (speed within the loop)...
Related
There are plenty of tips and code examples out there of accessing PHP arrays with dot notation, but I would like to do somewhat the opposite. I would like to take a multidimensional array like this:
$myArray = array(
'key1' => 'value1',
'key2' => array(
'subkey' => 'subkeyval'
),
'key3' => 'value3',
'key4' => array(
'subkey4' => array(
'subsubkey4' => 'subsubkeyval4',
'subsubkey5' => 'subsubkeyval5',
),
'subkey5' => 'subkeyval5'
)
);
And turn it into this (likely through some recursive function):
$newArray = array(
'key1' => 'value1',
'key2.subkey' => 'subkeyval',
'key3' => 'value3',
'key4.subkey4.subsubkey4' => 'subsubkeyval4',
'key4.subkey5.subsubkey5' => 'subsubkeyval5',
'key4.subkey5' => 'subkeyval5'
);
teh codez
$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($myArray));
$result = array();
foreach ($ritit as $leafValue) {
$keys = array();
foreach (range(0, $ritit->getDepth()) as $depth) {
$keys[] = $ritit->getSubIterator($depth)->key();
}
$result[ join('.', $keys) ] = $leafValue;
}
output
Array
(
[key1] => value1
[key2.subkey] => subkeyval
[key3] => value3
[key4.subkey4.subsubkey4] => subsubkeyval4
[key4.subkey4.subsubkey5] => subsubkeyval5
[key4.subkey5] => subkeyval5
)
demo: http://codepad.org/YiygqxTM
I need to go, but if you need an explanation of that tomorrow, ask me.
This will handle an arbitrary level of nesting:
<? //PHP 5.4+
$dotFlatten = static function(array $item, $context = '') use (&$dotFlatten){
$retval = [];
foreach($item as $key => $value){
if (\is_array($value) === true){
foreach($dotFlatten($value, "$context$key.") as $iKey => $iValue){
$retval[$iKey] = $iValue;
}
} else {
$retval["$context$key"] = $value;
}
}
return $retval;
};
var_dump(
$dotFlatten(
[
'key1' => 'value1',
'key2' => [
'subkey' => 'subkeyval',
],
'key3' => 'value3',
'key4' => [
'subkey4' => [
'subsubkey4' => 'subsubkeyval4',
'subsubkey5' => 'subsubkeyval5',
],
'subkey5' => 'subkeyval5',
],
]
)
);
?>
There is already the answer with RecursiveIteratorIterator. But here is a more optimal solution, that avoids using nested loops:
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($arr),
RecursiveIteratorIterator::SELF_FIRST
);
$path = [];
$flatArray = [];
foreach ($iterator as $key => $value) {
$path[$iterator->getDepth()] = $key;
if (!is_array($value)) {
$flatArray[
implode('.', array_slice($path, 0, $iterator->getDepth() + 1))
] = $value;
}
}
There are several points need to be made here. Notice the use of RecursiveIteratorIterator::SELF_FIRST constant here. It is important as the default one is RecursiveIteratorIterator::LEAVES_ONLY which wouldn't let us access all keys. So with this constant set, we start from the top level of an array and go deeper. This approach lets us store the history of keys and prepare the key when we rich leaf using RecursiveIteratorIterator::getDepth method.
Here is a working demo.
This is my take on a recursive solution, which works for arrays of any depth:
function convertArray($arr, $narr = array(), $nkey = '') {
foreach ($arr as $key => $value) {
if (is_array($value)) {
$narr = array_merge($narr, convertArray($value, $narr, $nkey . $key . '.'));
} else {
$narr[$nkey . $key] = $value;
}
}
return $narr;
}
Which can be called as $newArray = convertArray($myArray).
This another approach similar to Blafrat above - but handles simply arrays as values.
function dot_flatten($input_arr, $return_arr = array(), $prev_key = '')
{
foreach ($input_arr as $key => $value)
{
$new_key = $prev_key . $key;
// check if it's associative array 99% good
if (is_array($value) && key($value) !==0 && key($value) !==null)
{
$return_arr = array_merge($return_arr, dot_flatten($value, $return_arr, $new_key . '.'));
}
else
{
$return_arr[$new_key] = $value;
}
}
return $return_arr;
}
(The only case this wouldn't catch is where you had a value that was associative but the first key was 0.)
Note that the RecursiveIteratorIterator can be slower than regular recursive function.
https://xenforo.com/community/threads/php-spl-why-is-recursiveiteratoriterator-100x-slower-than-recursive-search.57572/
In this case using the sample array given for 1000 iterations php5.6, this code is twice as fast (recursive=.032 vs interator=.062) - but the difference is probably insignificant for most cases. Mainly I prefer recursive because I find the logic of the Iterator needlessly complicated for a simple use case like this.
There are plenty of tips and code examples out there of accessing PHP arrays with dot notation, but I would like to do somewhat the opposite. I would like to take a multidimensional array like this:
$myArray = array(
'key1' => 'value1',
'key2' => array(
'subkey' => 'subkeyval'
),
'key3' => 'value3',
'key4' => array(
'subkey4' => array(
'subsubkey4' => 'subsubkeyval4',
'subsubkey5' => 'subsubkeyval5',
),
'subkey5' => 'subkeyval5'
)
);
And turn it into this (likely through some recursive function):
$newArray = array(
'key1' => 'value1',
'key2.subkey' => 'subkeyval',
'key3' => 'value3',
'key4.subkey4.subsubkey4' => 'subsubkeyval4',
'key4.subkey5.subsubkey5' => 'subsubkeyval5',
'key4.subkey5' => 'subkeyval5'
);
teh codez
$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($myArray));
$result = array();
foreach ($ritit as $leafValue) {
$keys = array();
foreach (range(0, $ritit->getDepth()) as $depth) {
$keys[] = $ritit->getSubIterator($depth)->key();
}
$result[ join('.', $keys) ] = $leafValue;
}
output
Array
(
[key1] => value1
[key2.subkey] => subkeyval
[key3] => value3
[key4.subkey4.subsubkey4] => subsubkeyval4
[key4.subkey4.subsubkey5] => subsubkeyval5
[key4.subkey5] => subkeyval5
)
demo: http://codepad.org/YiygqxTM
I need to go, but if you need an explanation of that tomorrow, ask me.
This will handle an arbitrary level of nesting:
<? //PHP 5.4+
$dotFlatten = static function(array $item, $context = '') use (&$dotFlatten){
$retval = [];
foreach($item as $key => $value){
if (\is_array($value) === true){
foreach($dotFlatten($value, "$context$key.") as $iKey => $iValue){
$retval[$iKey] = $iValue;
}
} else {
$retval["$context$key"] = $value;
}
}
return $retval;
};
var_dump(
$dotFlatten(
[
'key1' => 'value1',
'key2' => [
'subkey' => 'subkeyval',
],
'key3' => 'value3',
'key4' => [
'subkey4' => [
'subsubkey4' => 'subsubkeyval4',
'subsubkey5' => 'subsubkeyval5',
],
'subkey5' => 'subkeyval5',
],
]
)
);
?>
There is already the answer with RecursiveIteratorIterator. But here is a more optimal solution, that avoids using nested loops:
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($arr),
RecursiveIteratorIterator::SELF_FIRST
);
$path = [];
$flatArray = [];
foreach ($iterator as $key => $value) {
$path[$iterator->getDepth()] = $key;
if (!is_array($value)) {
$flatArray[
implode('.', array_slice($path, 0, $iterator->getDepth() + 1))
] = $value;
}
}
There are several points need to be made here. Notice the use of RecursiveIteratorIterator::SELF_FIRST constant here. It is important as the default one is RecursiveIteratorIterator::LEAVES_ONLY which wouldn't let us access all keys. So with this constant set, we start from the top level of an array and go deeper. This approach lets us store the history of keys and prepare the key when we rich leaf using RecursiveIteratorIterator::getDepth method.
Here is a working demo.
This is my take on a recursive solution, which works for arrays of any depth:
function convertArray($arr, $narr = array(), $nkey = '') {
foreach ($arr as $key => $value) {
if (is_array($value)) {
$narr = array_merge($narr, convertArray($value, $narr, $nkey . $key . '.'));
} else {
$narr[$nkey . $key] = $value;
}
}
return $narr;
}
Which can be called as $newArray = convertArray($myArray).
This another approach similar to Blafrat above - but handles simply arrays as values.
function dot_flatten($input_arr, $return_arr = array(), $prev_key = '')
{
foreach ($input_arr as $key => $value)
{
$new_key = $prev_key . $key;
// check if it's associative array 99% good
if (is_array($value) && key($value) !==0 && key($value) !==null)
{
$return_arr = array_merge($return_arr, dot_flatten($value, $return_arr, $new_key . '.'));
}
else
{
$return_arr[$new_key] = $value;
}
}
return $return_arr;
}
(The only case this wouldn't catch is where you had a value that was associative but the first key was 0.)
Note that the RecursiveIteratorIterator can be slower than regular recursive function.
https://xenforo.com/community/threads/php-spl-why-is-recursiveiteratoriterator-100x-slower-than-recursive-search.57572/
In this case using the sample array given for 1000 iterations php5.6, this code is twice as fast (recursive=.032 vs interator=.062) - but the difference is probably insignificant for most cases. Mainly I prefer recursive because I find the logic of the Iterator needlessly complicated for a simple use case like this.
There are plenty of tips and code examples out there of accessing PHP arrays with dot notation, but I would like to do somewhat the opposite. I would like to take a multidimensional array like this:
$myArray = array(
'key1' => 'value1',
'key2' => array(
'subkey' => 'subkeyval'
),
'key3' => 'value3',
'key4' => array(
'subkey4' => array(
'subsubkey4' => 'subsubkeyval4',
'subsubkey5' => 'subsubkeyval5',
),
'subkey5' => 'subkeyval5'
)
);
And turn it into this (likely through some recursive function):
$newArray = array(
'key1' => 'value1',
'key2.subkey' => 'subkeyval',
'key3' => 'value3',
'key4.subkey4.subsubkey4' => 'subsubkeyval4',
'key4.subkey5.subsubkey5' => 'subsubkeyval5',
'key4.subkey5' => 'subkeyval5'
);
teh codez
$ritit = new RecursiveIteratorIterator(new RecursiveArrayIterator($myArray));
$result = array();
foreach ($ritit as $leafValue) {
$keys = array();
foreach (range(0, $ritit->getDepth()) as $depth) {
$keys[] = $ritit->getSubIterator($depth)->key();
}
$result[ join('.', $keys) ] = $leafValue;
}
output
Array
(
[key1] => value1
[key2.subkey] => subkeyval
[key3] => value3
[key4.subkey4.subsubkey4] => subsubkeyval4
[key4.subkey4.subsubkey5] => subsubkeyval5
[key4.subkey5] => subkeyval5
)
demo: http://codepad.org/YiygqxTM
I need to go, but if you need an explanation of that tomorrow, ask me.
This will handle an arbitrary level of nesting:
<? //PHP 5.4+
$dotFlatten = static function(array $item, $context = '') use (&$dotFlatten){
$retval = [];
foreach($item as $key => $value){
if (\is_array($value) === true){
foreach($dotFlatten($value, "$context$key.") as $iKey => $iValue){
$retval[$iKey] = $iValue;
}
} else {
$retval["$context$key"] = $value;
}
}
return $retval;
};
var_dump(
$dotFlatten(
[
'key1' => 'value1',
'key2' => [
'subkey' => 'subkeyval',
],
'key3' => 'value3',
'key4' => [
'subkey4' => [
'subsubkey4' => 'subsubkeyval4',
'subsubkey5' => 'subsubkeyval5',
],
'subkey5' => 'subkeyval5',
],
]
)
);
?>
There is already the answer with RecursiveIteratorIterator. But here is a more optimal solution, that avoids using nested loops:
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($arr),
RecursiveIteratorIterator::SELF_FIRST
);
$path = [];
$flatArray = [];
foreach ($iterator as $key => $value) {
$path[$iterator->getDepth()] = $key;
if (!is_array($value)) {
$flatArray[
implode('.', array_slice($path, 0, $iterator->getDepth() + 1))
] = $value;
}
}
There are several points need to be made here. Notice the use of RecursiveIteratorIterator::SELF_FIRST constant here. It is important as the default one is RecursiveIteratorIterator::LEAVES_ONLY which wouldn't let us access all keys. So with this constant set, we start from the top level of an array and go deeper. This approach lets us store the history of keys and prepare the key when we rich leaf using RecursiveIteratorIterator::getDepth method.
Here is a working demo.
This is my take on a recursive solution, which works for arrays of any depth:
function convertArray($arr, $narr = array(), $nkey = '') {
foreach ($arr as $key => $value) {
if (is_array($value)) {
$narr = array_merge($narr, convertArray($value, $narr, $nkey . $key . '.'));
} else {
$narr[$nkey . $key] = $value;
}
}
return $narr;
}
Which can be called as $newArray = convertArray($myArray).
This another approach similar to Blafrat above - but handles simply arrays as values.
function dot_flatten($input_arr, $return_arr = array(), $prev_key = '')
{
foreach ($input_arr as $key => $value)
{
$new_key = $prev_key . $key;
// check if it's associative array 99% good
if (is_array($value) && key($value) !==0 && key($value) !==null)
{
$return_arr = array_merge($return_arr, dot_flatten($value, $return_arr, $new_key . '.'));
}
else
{
$return_arr[$new_key] = $value;
}
}
return $return_arr;
}
(The only case this wouldn't catch is where you had a value that was associative but the first key was 0.)
Note that the RecursiveIteratorIterator can be slower than regular recursive function.
https://xenforo.com/community/threads/php-spl-why-is-recursiveiteratoriterator-100x-slower-than-recursive-search.57572/
In this case using the sample array given for 1000 iterations php5.6, this code is twice as fast (recursive=.032 vs interator=.062) - but the difference is probably insignificant for most cases. Mainly I prefer recursive because I find the logic of the Iterator needlessly complicated for a simple use case like this.
I'm trying to create a multidimensional array from a string (received from $_GET, input is validated, but not in this example). Each '-' will indicate a level in the multidimensional array.
Values can look like this (any form really, as long as '-' is present between keys). The array of values can map to any depth in the multidimensional array.
$array = array(
'page-title' => 'Title of a page',
'page-url' => 'http://www.mypage.com',
'meta-page-author' => 'Some guy',
'meta-page-created' => 'some timestamp'
);
I've tried different solutions, but the only thing working until now is the inital loop and extract of keys.
foreach ($array as $key => $value) {
if (strpos($key, '-') !== false) {
$keyArray = explode('-', $key);
// ??
}
}
The output I'm hoping for, should look like this:
array(
'page' => array(
'title' => 'Title of a page',
'url' => 'http://www.mypage.com'
),
'meta' => array(
'page' => array(
'author' => 'Some guy',
'created' => 'some timestamp'
)
)
);
Something like this should work:
<?php
$array = array(
'page-title' => 'Title of a page',
'page-url' => 'http://www.mypage.com',
'meta-page-author' => 'Some guy',
'meta-page-created' => 'some timestamp'
);
$result = array();
foreach ($array as $key => $value) {
$keys = strpos($key, '-') !== false ? explode('-', $key) : array($key);
$ptr = &$result;
foreach ($keys as $k) {
if (!isset($ptr[$k])) {
$ptr[$k] = array();
}
$ptr = &$ptr[$k];
}
if (empty($ptr)) {
$ptr = $value;
} else {
$ptr[] = $value;
}
}
print_r($result);
What I did was explode your keys just like you were doing. I then looped through them creating a new array if the array didn't already exist. Using a reference I save the current point I was at in the array. Then once I had hit the last key I assigned the value. Hope this helps.
EDIT: Based on cHao's recommendation I changed
$keys = strpos($key, '-') !== false ? explode('-', $key) : $key;
to
$keys = strpos($key, '-') !== false ? explode('-', $key) : array($key);
to prevent failure on the foreach.
EDIT 2: I changed
$ptr = $value;
to
if (empty($ptr)) {
$ptr = $value;
} else {
$ptr[] = $value;
}
to handle cases like:
$array = array(
'page-title' => 'Title of a page',
'page-url' => 'http://www.mypage.com',
'meta-page-author' => 'Some guy',
'meta-page-created' => 'some timestamp',
'page' => 'foo'
);
Just so you're aware, PHP can be made to accept whole big arrays like that. If you name the form elements like 'somename[page][title]', then when the form returns, you should see them already arranged as an array in $_GET.
In case you have your heart set on the current naming scheme, though...
$result = array();
foreach ($array as $key => $value) {
$current =& $result;
if (strpos($key, '-') !== false) {
$keyArray = explode('-', $key);
$bottomKey = array_pop($keyArray);
foreach ($keyArray as $subKey) {
if (!isset($current[$subKey]))
$current[$subKey] = array();
$current =& $current[$subKey];
}
} else {
$bottomKey = $key;
}
$current[$bottomKey] = $value;
}
<?php
$array = array(
'page-title' => 'Title of a page',
'page-url' => 'http://www.mypage.com',
'meta-page-author' => 'Some guy',
'meta-page-created' => 'some timestamp'
);
$result = array();
foreach ($array as $key => $value) {
if (strpos($key, '-') !== false) {
$ak = "result['" . str_replace('-', '\'][\'', $key) . "'] = \"".$value."\"";
eval('$'.$ak.';');
}
}
var_dump($result);
?>
hope that helps
In an array such as the one below, how could I rename "fee_id" to "id"?
Array
(
[0] => Array
(
[fee_id] => 15
[fee_amount] => 308.5
[year] => 2009
)
[1] => Array
(
[fee_id] => 14
[fee_amount] => 308.5
[year] => 2009
)
)
foreach ( $array as $k=>$v )
{
$array[$k] ['id'] = $array[$k] ['fee_id'];
unset($array[$k]['fee_id']);
}
This should work
You could use array_map() to do it.
$myarray = array_map(function($tag) {
return array(
'id' => $tag['fee_id'],
'fee_amount' => $tag['fee_amount'],
'year' => $tag['year']
); }, $myarray);
$arrayNum = count($theArray);
for( $i = 0 ; $i < $arrayNum ; $i++ )
{
$fee_id_value = $theArray[$i]['fee_id'];
unset($theArray[$i]['fee_id']);
$theArray[$i]['id'] = $fee_id_value;
}
This should work.
Copy the current 'fee_id' value to a new key named 'id' and unset the previous key?
foreach ($array as $arr)
{
$arr['id'] = $arr['fee_id'];
unset($arr['fee_id']);
}
There is no function builtin doing such thin afaik.
This is the working solution, i tested it.
foreach ($myArray as &$arr) {
$arr['id'] = $arr['fee_id'];
unset($arr['fee_id']);
}
The snippet below will rename an associative array key while preserving order (sometimes... we must). You can substitute the new key's $value if you need to wholly replace an item.
$old_key = "key_to_replace";
$new_key = "my_new_key";
$intermediate_array = array();
while (list($key, $value) = each($original_array)) {
if ($key == $old_key) {
$intermediate_array[$new_key] = $value;
}
else {
$intermediate_array[$key] = $value;
}
}
$original_array = $intermediate_array;
Converted 0->feild0, 1->field1,2->field2....
This is just one example in which i get comma separated value in string and convert it into multidimensional array and then using foreach loop i changed key value of array
<?php
$str = "abc,def,ghi,jkl,mno,pqr,stu
abc,def,ghi,jkl,mno,pqr,stu
abc,def,ghi,jkl,mno,pqr,stu
abc,def,ghi,jkl,mno,pqr,stu;
echo '<pre>';
$arr1 = explode("\n", $str); // this will create multidimensional array from upper string
//print_r($arr1);
foreach ($arr1 as $key => $value) {
$arr2[] = explode(",", $value);
foreach ($arr2 as $key1 => $value1) {
$i =0;
foreach ($value1 as $key2 => $value2) {
$key3 = 'field'.$i;
$i++;
$value1[$key3] = $value2;
unset($value1[$key2]);
}
}
$arr3[] = $value1;
}
print_r($arr3);
?>
I wrote a function to do it using objects or arrays (single or multidimensional) see at https://github.com/joaorito/php_RenameKeys.
Bellow is a simple example, you can use a json feature combine with replace to do it.
// Your original array (single or multi)
$original = array(
'DataHora' => date('YmdHis'),
'Produto' => 'Produto 1',
'Preco' => 10.00,
'Quant' => 2);
// Your map of key to change
$map = array(
'DataHora' => 'Date',
'Produto' => 'Product',
'Preco' => 'Price',
'Quant' => 'Amount');
$temp_array = json_encode($original);
foreach ($map AS $k=>$v) {
$temp_array = str_ireplace('"'.$k.'":','"'.$v.'":', $temp);
}
$new_array = json_decode($temp, $array);
Multidimentional array key can be changed dynamically by following function:
function change_key(array $arr, $keySetOrCallBack = [])
{
$newArr = [];
foreach ($arr as $k => $v) {
if (is_callable($keySetOrCallBack)) {
$key = call_user_func_array($keySetOrCallBack, [$k, $v]);
} else {
$key = $keySetOrCallBack[$k] ?? $k;
}
$newArr[$key] = is_array($v) ? array_change_key($v, $keySetOrCallBack) : $v;
}
return $newArr;
}
Sample Example:
$sampleArray = [
'hello' => 'world',
'nested' => ['hello' => 'John']
];
//Change by difined key set
$outputArray = change_key($sampleArray, ['hello' => 'hi']);
//Output Array: ['hi' => 'world', 'nested' => ['hi' => 'John']];
//Change by callback
$outputArray = change_key($sampleArray, function($key, $value) {
return ucwords(key);
});
//Output Array: ['Hello' => 'world', 'Nested' => ['Hello' => 'John']];
I have been trying to solve this issue for a couple hours using recursive functions, but finally I realized that we don't need recursion at all. Below is my approach.
$search = array('key1','key2','key3');
$replace = array('newkey1','newkey2','newkey3');
$resArray = str_replace($search,$replace,json_encode($array));
$res = json_decode($resArray);
On this way we can avoid loop and recursion.
Hope It helps.