Consider an associative array of arbitrary form and nesting depth, for example:
$someVar = array(
'name' => 'Dotan',
'age' => 35,
'children' => array(
0 => array(
'name' => 'Meirav',
'age' => 6,
),
1 => array(
'name' => 'Maayan',
'age' => 4,
)
),
'dogs' => array('Gili', 'Gipsy')
);
I would like to convert this to an associative array of paths and values:
$someVar = array(
'name' => 'Dotan',
'age' => 35,
'children/0/name' => 'Meirav',
'children/0/age' => 6,
'children/1/name' => 'Maayan',
'children/1/age' => 4,
'dogs/0' => 'Gili',
'dogs/1' => 'Gipsy'
);
I began writing a recursive function which for array elements would recurse and for non-array elements (int, floats, bools, and strings) return an array $return['path'] and $return['value']. This got sloppy quick! Is there a better way to do this in PHP? I would assume that callables and objects would not be passed in the array, though any solution which deals with that possibility would be best. Also, I am assuming that the input array would not have the / character in an element name, but accounting for that might be prudent! Note that the input array could be nested as deep as 8 or more levels deep!
Recursion is really the only way you'll be able to handle this, but here's a simple version to start with:
function nested_values($array, $path=""){
$output = array();
foreach($array as $key => $value) {
if(is_array($value)) {
$output = array_merge($output, nested_values($value, (!empty($path)) ? $path.$key."/" : $key."/"));
}
else $output[$path.$key] = $value;
}
return $output;
}
function getRecursive($path, $node) {
if (is_array($node)) {
$ret = '';
foreach($node as $key => $val)
$ret .= getRecursive($path.'.'.$key, $val);
return $ret;
}
return $path.' => '.$node."\n";
}
$r = getRecursive('', $someVar);
print_r($r);
All yours to place it in an array.
Related
I'm using array_filter in PHP to split an array containing multiple arrays when the value of a key named type matches a specific string. Here's what this looks like:
Sample Array
$arr[] = Array (
[0] => Array ( [type] => Recurring ... )
[1] => Array ( [type] => Single ... )
)
Functions
function recurring($value)
{
return ($value['type'] == 'Recurring');
}
function single($value)
{
return ($value['type'] == 'Single');
}
Split Arrays
$recurring = array_filter($arr, 'recurring');
$single = array_filter($arr, 'single');
This works, but I was curious if there was a way to simplify it so that I could create additional filtered arrays in the future without creating a new function for each.
I've started setting up a single function using a closure, but I'm not sure how to do it. Any ideas?
function key_type($value, $key, $string) {
return $key == 'type' && $value == $string;
}
$recurring = array_filter($arr,
key_type('Recurring'), ARRAY_FILTER_USE_BOTH);
$single = array_filter($pricing,
key_type('Single'), ARRAY_FILTER_USE_BOTH);
You could actually do what you proposed in your question. You just need to have the key_type() function return a callable function, which is what array_filter expects as the second parameter. You can return an anonymous function and pass the argument into the anonymous function using the use keyword as CBroe mentioned in the comments.
Here is an example:
function key_type($key) {
return function($value) use ($key) {
return $value['type'] == $key;
};
}
$arr = array(
array('type'=>'Recurring'),
array('type'=>'Single')
);
print_r(array_filter($arr, key_type('Single'), ARRAY_FILTER_USE_BOTH));
The above code will output:
Array ( [1] => Array ( [type] => Single ) )
The beauty of this method is that if you need to change the logic for all instances where you need to use your filter, you just have to change it one time in your key_type function.
An approach would be like below, however I don't like it honestly.
$array = [['type' => 'Single'], ['type' => 'Recurring']];
function key_type($value) {
global $string;
return $value['type'] == $string;
}
($string = 'Recurring') && ($recurring = array_filter($array, 'key_type'));
($string = 'Single') && ($single = array_filter($array, 'key_type'));
Another way to achieve same thing is using Anonymous functions (closures). Don't think much about being DRY it seems nice:
$array = [['type' => 'Single'], ['type' => 'Recurring']];
$recurring = array_filter($array, function($value) {
return $value['type'] == 'Recurring';
});
$single = array_filter($array, function($value) {
return $value['type'] == 'Single';
});
This task might be more about grouping than filtering -- it is difficult to discern from the limited sample data.
As a general rule, I strongly advise against using variable variables in PHP code. It is better practice to store data in arrays for accessibility reasons.
If you only have the two mentioned type values in your project data, then the conditional can be removed entirely.
Code: (Demo)
$array = [
['type' => 'Recurring', 'id' => 1],
['type' => 'Single', 'id' => 2],
['type' => 'Other', 'id' => 3],
['type' => 'Recurring', 'id' => 4],
['type' => 'Single', 'id' => 5],
];
$result = [];
foreach ($array as $row) {
if (in_array($row['type'], ['Recurring', 'Single'])) {
$result[strtolower($row['type'])][] = $row;
}
}
var_export($result);
Output:
array (
'recurring' =>
array (
0 =>
array (
'type' => 'Recurring',
'id' => 1,
),
1 =>
array (
'type' => 'Recurring',
'id' => 4,
),
),
'single' =>
array (
0 =>
array (
'type' => 'Single',
'id' => 2,
),
1 =>
array (
'type' => 'Single',
'id' => 5,
),
),
)
I have a multi array e.g
$a = array(
'key' => array(
'sub_key' => 'val'
),
'dif_key' => array(
'key' => array(
'sub_key' => 'val'
)
)
);
The real array I have is quite large and the keys are all at different positions.
I've started to write a bunch of nested foreach and if/isset but it's not quite working and feels a bit 'wrong'. I'm fairly familiar with PHP but a bit stuck with this one.
Is there a built in function or a best practise way that I can access all values based on the key name regardless of where it is.
E.g get all values from 'sub_key' regardless of position in array.
EDIT: I see now the problem is that my "sub_key" is an array and therefore not included in the results as per the first comment here http://php.net/manual/en/function.array-walk-recursive.php
Just try with array_walk_recursive:
$output = [];
array_walk_recursive($input, function ($value, $key) use (&$output) {
if ($key === 'sub_key') {
$output[] = $value;
}
});
Output:
array (size=2)
0 => string 'val' (length=3)
1 => string 'val' (length=3)
You can do something like
$a = [
'key' => [
'sub_key' => 'val'
],
'dif_key' => [
'key' => [
'sub_key' => 'val'
]
]
];
$values = [];
array_walk_recursive($a, function($v, $k, $u) use (&$values){
if($k == "sub_key") {
$values[] = $v;
}
}, $values );
print_r($values);
How does it work?
array_walk_recursive() walks by every element of an array resursivly and you can apply user defined function. I created an anonymous function and pass an empty array via reference. In this anonymous function I check if element key is correct and if so it adds element to array. After function is applied to every element you will have values of each key that is equal to "sub_key"
What I'd like to do is have a function that accepts two arguments, both arrays, the first being a one-dimensional array of varying lengths and the second is a multi-dimensional array of varying depths and lengths. The first array is never associative, the second is always a fully associative array.
This function would return the requested value from the multi-dimensional array as indicated by the first array.
Assume that the first array will always be hand-written and passed to this function. Meaning the developer always knows there is a value to be returned from the multi-dimensional array and would never pass a request to the function where a value did not exist.
I think the code below is the best example at what I'm trying to achieve.
//Example multi-dimensional array
$multi = array(
'fruit' => array(
'red' => array(
'strawberries' => '$2.99/lb',
'apples' => '$1.99/lb'
),
'green' => array(
'honeydew' => '$3.39/lb',
'limes' => '$0.75/lb'
)
),
'vegetables' => array(
'yellow' => array(
'squash' => '$1.29/lb',
'bellpepper' => '$0.99/lb'
),
'purple' => array(
'eggplant' => '$2.39/lb'
)
),
'weeklypromo' => '15% off',
'subscribers' => array(
'user1#email.com' => 'User 1',
'user2#email.com' => 'User 2',
'user3#email.com' => 'User 3',
'user4#email.com' => 'User 4'
)
);
//Example one-dimensional array
$single = array('fruit', 'red', 'apples');
function magicfunc($single, $multi) {
//some magic here that looks something like below
$magic_value = $multi[$single[0]][$single[1]][$single[2]];
return $magic_value;
}
//Examples:
print magicfunc(array('fruit', 'red', 'apples'), $multi);
Output:
$1.99/lb
print magicfunc(array('subscribers', 'user3#email.com'), $multi);
Output:
User 3
print magicfunc(array('weeklypromo'), $multi);
Output:
15% off
This returns the values as requested:
function magicfunc($single, $multi) {
while (true) {
if (!$single) {
break;
}
$searchIndex = array_shift($single);
foreach ($multi as $k => $val) {
if ($k == $searchIndex) {
$multi = $val;
continue 2;
}
}
}
return $multi;
}
I have
$map = array('id' => 'clmId', 'name' => 'clmName' => 'value' => 'clmValue',);
$value = array('id' => 1, 'name' => 'Foo', 'value' => 'Bar',);
and I want to get
$expected = array('clmId' => 1, 'clmName' => 'Foo', 'clmValue' => 'Bar');
of course I did $expected = array_combine($map, $value) and it works most of the time, but fails (to my surprise) for following
$map = array('id' => 'clmId', 'name' => 'clmName' => 'value' => 'clmValue',);
$value = array('name' => 'Foo', 'id' => 1, 'value' => 'Bar',);
$expected = array_combine($map, $value);
//you get
//$expected = array('clmId' => Foo, 'clmName' => 1, 'clmValue' => 'Bar');
clearly, array_combine is not meant for combining associative arrays. What can be done to achieve this ?
I am doing a primitive foreach($map as $key => $mapValue) { ... but I am guessing a smarter map/reduce or some cool array function should do it for me.
Return array()/FALSE in case $value has no corresponding key from $map
function combine_if_same_keys( $array_one, $array_two ) {
$expected = false;
ksort($array_one);
ksort($array_two);
$diff = array_diff_key($array_one, $array_two);
if( empty($diff) && count($array_one) == count($array_two) ) {
$expected = array_combine( $array_one, $array_two );
}
return $expected;
}
Returns false if the keys don't match and an array if they did match.
$map = array('id' => 'clmId', 'name' => 'clmName', 'value' => 'clmValue');
$value = array('id' => 1, 'name' => 'Foo', 'value' => 'Bar');
$value2 = array('ids' => 1, 'name' => 'Foo', 'value' => 'Bar');
var_dump( combine_if_same_keys( $map, $value ) );
var_dump( combine_if_same_keys( $map, $value2 ) );
Outputs:
array(3) { ["clmId"]=> int(1) ["clmName"]=> string(3) "Foo" ["clmValue"]=> string(3) "Bar" }
bool(false)
Edit: Benchmarking
Just read some comments on another answer which suggested that ksort() will cause a performance hit, so I did a bit of benchmarking Ran ksort() on 2 arrays with (albeit numeric keyed arrays) 10,000,000 keys each which only took 0.010757923126221 seconds on
Intel Q8200 # 2.33ghz (4CPUs), 3072MB RAM, Windows 7 x64 .
Edit (by OP): Number of keys should match as well
array_diff_key($a1, $a2) returns empty array even if count($a2) > count($a1), the array_combine returns FALSE while generating a warning.
One can suppress it by #array_combine, but I would rather put the count condition (along with the appropriate empty() test on the array_key_diff return) and then combine the array.
I think there is no builtin way
function combine_assoc($map, $values) {
$output = array();
foreach($map as $key => $values) {
if(!array_key_exists($key, $value)) return FALSE;
$output[$value] = $values[$key];
}
return $output;
}
Of course, you could simply sort your arrays by key first, but this does not take care of missing key/value pairs and has a somewhat decreased performance:
ksort($map);
ksort($value);
$output = array_combine($map, $value);
Version without foreach loop, which does check for matching keys, but I do not recommend it, since it will not perform well …
function combine_assoc_slow($map, $value) {
ksort($map);
ksort($value);
if(array_keys($map) != array_keys($value)) return FALSE;
return array_combine($map, $value);
}
So array_combine doesn't work if the order is different? Well, easiest solution for you: sorting both arrays by key!
$map = array('id' => 'clmId', 'name' => 'clmName' => 'value' => 'clmValue');
$value = array('name' => 'Foo', 'id' => 1, 'value' => 'Bar');
ksort($map);
ksort($value);
// keys are "aligned", ready to combine
$expected = array_combine($map, $value);
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
recursive array_diff()?
I have a static multidimensional array which is always going to be in the same form. E.g. it will have the same keys & hierarchy.
I want to check a posted array to be in the same 'form' as this static array and if not error.
I have been trying various methods but they all seem to end up with a lot of if...else components and are rather messy.
Is there a succinct way to achieve this?
In response to an answer from dfsq:
$etalon = array(
'name' => array(),
'income' => array(
'day' => '',
'month' => array(),
'year' => array()
),
'message' => array(),
);
$test = array(
'name' => array(),
'income' => array(
'day' => '',
'month' => array(),
'year' => array()
),
'message' => array(),
);
// Tests
$diff = array_diff_key_recursive($etalon, $test);
var_dump(empty($diff));
print_r($diff);
And the results from that are
bool(false)
Array ( [name] => 1 [income] => Array ( [month] => 1 [year] => 1 ) [message] => 1 )
Author needs a solution which would test if the structure of the arrays are the same. Next function will make a job.
/**
* $a1 array your static array.
* $a2 array array you want to test.
* #return array difference between arrays. empty result means $a1 and $a2 has the same structure.
*/
function array_diff_key_recursive($a1, $a2)
{
$r = array();
foreach ($a1 as $k => $v)
{
if (is_array($v))
{
if (!isset($a2[$k]) || !is_array($a2[$k]))
{
$r[$k] = $a1[$k];
}
else
{
if ($diff = array_diff_key_recursive($a1[$k], $a2[$k]))
{
$r[$k] = $diff;
}
}
}
else
{
if (!isset($a2[$k]) || is_array($a2[$k]))
{
$r[$k] = $v;
}
}
}
return $r;
}
And test it:
$etalon = array(
'name' => '',
'income' => array(
'day' => '',
'month' => array(),
'year' => array()
),
'message' => ''
);
$test = array(
'name' => 'Tomas Brook',
'income' => array(
'day' => 123,
'month' => 123,
'year' => array()
)
);
// Tests
$diff = array_diff_key_recursive($etalon, $test);
var_dump(empty($diff));
print_r($diff);
This will output:
bool(false)
Array
(
[income] => Array
(
[month] => Array()
)
[message] =>
)
So checking for emptiness of $diff array will tell you if arrays have the same structure.
Note: if you need you can also test it in other direction to see if test array has some extra keys which are not present in original static array.
You could user array_intersect_key() to check if they both contain the same keys. If so, the resulting array from that function will contain the same values as array_keys() on the source array.
AFAIK those functions aren't recursive, so you'd have to write a recursion wrapper around them.
See User-Notes on http://php.net/array_diff_key
Are you searching for the array_diff or array_diff_assoc functions?
use foreach with the ifs...if u have different tests for the different inner keys eg
$many_entries = ("entry1" = array("name"=>"obi", "height"=>10));
and so on, first define functions to check the different keys
then a foreach statement like this
foreach($many_entries as $entry)
{
foreach($entry as $key => $val)
{
switch($key)
{
case "name": //name function
//and so on
}
}
}