How to find a key in multidimensional array? - php

I have a multidimensional array and i need make a searchCategory($categories, $id) function, which have to return a value of 'title' properties.
it try this code, it work but only for one layer of multidimensional array.
Multidimensional array:
$categories = array( array(
"id" => 1,
"title" => "Обувь",
'children' => array(
array(
'id' => 2,
'title' => 'Ботинки',
'children' => array(
array('id' => 3, 'title' => 'Кожа'),
array('id' => 4, 'title' => 'Текстиль'),
),
),
array('id' => 5, 'title' => 'Кроссовки',),
)), array(
"id" => 6,
"title" => "Спорт",
'children' => array(
array(
'id' => 7,
'title' => 'Мячи'
)
) ), );
Code which i try solve problem:
function searchCategory($categories, $id) {
foreach($categories as $category) {
if($category['id'] == $id) {
echo $category['title'] . '<br>';
}
}
};
I need my function to look up the id value in all arrays and return the title in the case of the array found

Here is recursive iterator case for you, please go through inline doc for explanation
function searchCategory($categories, $id)
{
$arrayiter = new RecursiveArrayIterator($categories);
$iteriter = new RecursiveIteratorIterator($arrayiter);
foreach ($iteriter as $key => $value) {
// checking if iterator comes to point where key is id and value matched
if ($key == 'id' && $value == $id) {
// returning matched value with current iterator instance
return $iteriter->getInnerIterator()['title'];
}
}
return '';
}
echo searchCategory($categories, 2).'<br/>';
echo searchCategory($categories, 7);
Working demo.
RecursiveArrayIterator - This iterator allows to unset and modify values and keys while iterating over Arrays and Objects in the same way as the ArrayIterator. Additionally it is possible to iterate over the current iterator entry.
RecursiveIteratorIterator - Can be used to iterate through recursive iterators.
RecursiveIteratorIterator::getInnerIterator: Get inner iterator

you need to make your function recursively
Demo : https://3v4l.org/CR7CD
function searchCategory($categories, $id) {
foreach($categories as $category) {
if($category['id'] == $id)
echo $category['title'] . '<br>';
if(isset($category['children']))
searchCategory($category['children'],$id);
}
}

Related

Add and modify value in a deep nested mixed array

Let's say you have an array like this:
$list = array(
'name' => 'foobar',
'id' => '12302',
'group' => array(array(
'name' => 'teamA',
'members' => array(
array(
'ID' => 'OAHSJLASJ8888'
'name' => 'eric',
'fname' => 'lu',
'age' => '22'
),
array(
'ID' => 'OKZ8JJLJYYH6'
'name' => 'franz',
'fname' => 'as',
'age' => '33'
),
array(
'ID' => 'OKOIYHJKKK'
'name' => 'Amr',
'fname' => 'ok',
'age' => '13'
)
)
),
array(
'name' => 'teamB',
'members' => array(
array(
'ID' => 'FGZ9ILKA'
'name' => 'Evan',
'fname' => 'lu',
'age' => '22'
),
array(
'ID' => 'KMLML2039KKK'
'name' => 'Michel',
'fname' => 'as',
'age' => '33'
),
array(
'ID' => 'AAA2039KKK'
'name' => 'Nickr',
'fname' => 'ok',
'age' => '13'
)
)
)
)
);
You want to add a value to the associative array named Amr which is the third element of the member key of the group key $list[group][0][members][2][newKey] = B
Using recursive function and foreach, I'm able to find anything I'm aiming at. Using array_walk_recursive I can also find the targeted key value and modify it.
Using RecursiveIteratorIterator and foreach, I can also find the element and modify it's value.
My issue is that I can not replace the modified object within the tree. I can follow the path down, but I'm not able to climb the tree back. I could maintain a index of each array I traverse and then recalculate the path to the key, but it looks culprit to me.
I can not modify the data structure, the dataset I have is as is.
Thanks for any help you could bring.
Code for Iterator:
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($list));
foreach($iterator as $key=>$value) {
if ($key === 'ID') {
$metas = get_relatedmeta_objects($value),true));
//metas key should be added to the current array
}
}
Recursive method:
function searchKeyAndAdd( &$element) {
if(is_array($element) || is_object($element)){
foreach ( $element as &$key => $value ) {
if ($key === "ID") {
$metas = get_relatedmeta_objects($value);
//metas key should be added to the current array
} else if (is_array($value)) {
searchObject($value);
}
}
}
}
array_walk_recursive method:
function alterArray(&$item, $key, &$parentRec) {
if (is_array($item) || is_object($item)) {
searchObject($item);
}
if ($key === 'ID') {
$parentRec = json_decode(json_encode($parentRec), true);
$parentRec['metas'] = get_field_objects($item);
// the current array is modified but the value does not go back to the $list initial array.
}
}
function searchObject( &$element, &$parent) {
array_walk_recursive($element, 'alterArray', $element);
}
The data set could be anything. You do not know the key, you just know that some nested object can have ID key and when they do you want to add more content to this object.
The recursive function can do it, but you should use the & prefix on $value instead of $key:
function searchKeyAndAdd( &$element) {
if(is_array($element) || is_object($element)){
foreach ( $element as $key => &$value ) {
if ($key === "ID") {
$element['meta'] = get_relatedmeta_objects($value);
} else {
searchKeyAndAdd($value);
}
}
}
}
searchKeyAndAdd($list);
The other two methods offer no reference to the parent, although in the case of array_walk_recursive you tried it with the third argument, but there things get messy: to make it work on each recursive depth, you call array_walk_recursive recursively... but array_walk_recursive already visits all the key/value pairs recursively. So this will lead to many calls to alterArray with the same key/value, but with a different ancestor as third argument for each of them.
Furthermore, with this line:
$parentRec = json_decode(json_encode($parentRec), true);
... you lose the reference to the original $parentRec, and so any modification you make to $parentRec will no longer have an effect on the original array.

Get path and value of all elements in nested associative array

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.

Know the element level in multidimensional array

Well, I am here again dealing with arrays in php. I need your hand to guide me in the right direction. Suppose the following array:
-fruits
--green
---limon
---mango
--red
---apple
-cars
--ferrari
---enzo
----blue
----black
---318
--lamborg
---spider
---gallardo
----gallado-96
-----blue
-----red
-----gallado-98
The - (hyphen) symbol only illustrates the deep level.
Well, I need to build another array (or whatever), because it should be printed as an HTML select as below:
-fruits
--green
---limon
---mango
--red
---apple
-cars
--ferrari
---enzo
----blue
----black
---318
--lamborg
---spider
---gallardo
----gallado-96
-----blue
-----red
-----gallado-98
Looks that for each level element, it should add a space, or hyphen to determinate that it belongs to a particular parent.
EDIT
The have provide an answer provideng my final code. The html select element will display each level as string (repeating the "-" at the begging of the text instead multi-level elements.
Here's a simple recursive function to build a select dropdown given an array. Unfortunately I'm not able to test it, but let me know if it works. Usage would be as follows:
function generateDropdown($array, $level = 1)
{
if ($level == 1)
{
$menu = '<select>';
}
foreach ($array as $a)
{
if (is_array($a))
{
$menu .= generateDropdown($a, $level+1);
}
else
{
$menu .= '<option>'.str_pad('',$level,'-').$a.'</option>'."\n";
}
}
if ($level == 1)
{
$menu = '</select>';
}
return $menu;
}
OK, I got it with the help of #jmgardhn2.
The data
This is my array:
$temp = array(
array(
'name' => 'fruits',
'sons' => array(
array(
'name' => 'green',
'sons' => array(
array(
'name' => 'mango'
),
array(
'name' => 'banana',
)
)
)
)
),
array(
'name' => 'cars',
'sons' => array(
array(
'name' => 'italy',
'sons' => array(
array(
'name' => 'ferrari',
'sons' => array(
array(
'name' => 'red'
),
array(
'name' => 'black'
),
)
),
array(
'name' => 'fiat',
)
)
),
array(
'name' => 'germany',
'sons' => array(
array(
'name' => 'bmw',
)
)
),
)
)
);
Recursive function
Now, the following function will provide an array with items like [level] => [name]:
function createSelect($tree, $items, $level)
{
foreach ($tree as $key)
{
if (is_array($key))
{
$items = createSelect($key, $items, $level + 1);
}
else
{
$items[] = array('level' => $level, 'text' => $key);
}
}
return $items;
}
Calling the funcion
Now, call the function as below:
$items = createSelect($temp, array(), 0);
Output
If you iterate the final $items array it will look like:
1fruits
2green
3mango
3banana
1cars
2italy
3ferrari
4red
4black
3fiat
2germany
3bmw

Quick Recursive search of all indexes within an array

Ok, so say I have an array as follows:
$buttons = array(
'mlist' => array(
'title' => 'Members',
'href' => $scripturl . '?action=mlist',
'show' => $context['allow_memberlist'],
'sub_buttons' => array(
'mlist_view' => array(
'title' => 'View the Member List',
'href' => $scripturl . '?action=mlist',
'show' => true,
),
'mlist_search' => array(
'title' => 'Search for Members',
'href' => $scripturl . '?action=mlist;sa=search',
'show' => true,
'is_last' => true,
),
),
),
'home' => array(
'title' => 'Home',
'href' => $scripturl,
'show' => true,
'sub_buttons' => array(
),
'is_last' => $context['right_to_left'],
),
'help' => array(
'title' => 'Help',
'href' => $scripturl . '?action=help',
'show' => true,
'sub_buttons' => array(
),
),
);
I need to sort through this array and return all indexes of it in another array as an index, and the values of these arrays will be the title. So it should return an array as follows:
array(
'mlist' => 'Members',
'mlist_view' => 'View the Member List',
'mlist_search' => 'Search for Members',
'home' => 'Home',
'help' => 'Help',
);
How can this be achieved easily? Basically, need the key of each array if a title is specified and need to populate both within another array.
The following snippet loops over all of the arrays (recursively) to extract the key/title pairs.
$index = array();
$iterator = new RecursiveIteratorIterator(new ParentIterator(new RecursiveArrayIterator($buttons)), RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $key => $value) {
if (array_key_exists('title', $value)) {
$index[$key] = $value['title'];
}
}
var_dump($index);
How can this be achieved easily?
initialize an empty, new array
foreach the $buttons array with key and value
extract title from value
set the key in the new array with the title
done.
Edit: In case a recursive array iterator catches too much (identifying elements as children while they are not - just being some other array), and you don't want to write an extension of the recursive iterator class, stepping through all children can be solved with some "hand written" iterator like this:
$index = array();
$childKey = 'sub_buttons';
$iterator = $buttons;
while(list($key, $item) = each($iterator))
{
array_shift($iterator);
$index[$key] = $item['title'];
$children = isset($item[$childKey]) ? $item[$childKey] : false;
if ($children) $iterator = $children + $iterator;
}
This iterator is aware of the child key, so it will only iterate over childs if there are some concrete. You can control the order (children first, children last) by changing the order:
if ($children) $iterator = $children + $iterator;
- or -
if ($children) $iterator += $children;
I'm sure my answer is not most efficient, but using many foreach loops and if checks, it can be done. However, with my solution if you nested another array inside of say 'mlist_view' that you needed to get a title from, it would not work. My solution works for a max of 2 arrays inside of arrays within buttons. A better (and more general purpose solution) would probably involve recursion.
$result = array();
foreach($buttons as $field => $value) {
foreach($value as $nF => $nV) {
if($nF === 'title') {
$result[$field] = $nV;
}
if(is_array($nV)) {
foreach($nV as $name => $comp) {
if(is_array($comp)) {
foreach($comp as $nnF => $nnV) {
if($nnF === 'title') {
$result[$name] = $nnV;
}
}
}
}
}
}
}
foreach($result as $f => $v) {
echo $f.": ".$v."<br/>";
}
This works for your value of $buttons, fairly simple:
function get_all_keys($arr) {
if (!is_array($arr)) return array();
$return = array();
foreach (array_keys($arr) as $key) {
if (is_array($arr[$key])
&& array_key_exists('title', $arr[$key]))
$return[$key] = $arr[$key]['title'];
$return = array_merge($return, get_all_keys($arr[$key]));
}
return $return;
}
echo "<pre>";
print_r(get_all_keys($buttons));
echo "</pre>";
Which returns:
Array
(
[mlist] => Members
[mlist_view] => View the Member List
[mlist_search] => Search for Members
[home] => Home
[help] => Help
)

Isolate a single column in a multi-dimensional array

Say for example you just queried a database and you recieved this 2D array.
$results = array(
array('id' => 1, 'name' => 'red' , 'spin' => 1),
array('id' => 2, 'name' => 'green', 'spin' => -1),
array('id' => 3, 'name' => 'blue' , 'spin' => .5)
);
I often find myself writing loops like this.
foreach($results as $result)
$names[] = $result['name'];
My questions is does there exist a way to get this array $names without using a loop? Using callback functions count as using a loop.
Here is a more generic example of getting every field.
foreach($results as $result)
foreach($result as $key => $value)
$fields[$key][] = $value;
As of June 20th in PHP-5.5 there is a new function array_column
For example:
$records = array(
array(
'id' => 2135,
'first_name' => 'John',
'last_name' => 'Doe'
),
array(
'id' => 3245,
'first_name' => 'Sally',
'last_name' => 'Smith'
),
array(
'id' => 5342,
'first_name' => 'Jane',
'last_name' => 'Jones'
),
array(
'id' => 5623,
'first_name' => 'Peter',
'last_name' => 'Doe'
)
);
$firstNames = array_column($records, 'first_name');
print_r($firstNames);
Will return
Array
(
[0] => John
[1] => Sally
[2] => Jane
[3] => Peter
)
There are even more examples in the above mentioned link.
I voted #Devon's response up because there really isn't a way to do what you're asking with a built-in function. The best you can do is write your own:
function array_column($array, $column)
{
$ret = array();
foreach ($array as $row) $ret[] = $row[$column];
return $ret;
}
Starting PHP 5.3, you can use this pretty call with lambda function:
$names = array_map(function ($v){ return $v['name']; }, $results);
This will return array sliced by 'name' dimension.
Simply put, no.
You will need to use a loop or a callback function like array_walk.
I did more research on this and found that ruby and prototype both have a function that does this called array_pluck,2. It's interesting that array_map has a second use that allows you to do the inverse of what i want to do here. I also found a PHP class someone is writing to emulate prototypes manipulation of arrays.
I'm going to do some more digging around and if I don't find anything else I'll work on a patch to submit to the internals#lists.php.net mailing list and see if they will add array_pluck.
For those of you that cannot upgrade to PHP5.5 right now and need this function, here is an implementation of array_column.
function array_column($array, $column){
$a2 = array();
array_map(function ($a1) use ($column, &$a2){
array_push($a2, $a1[$column]);
}, $array);
return $a2;
}
If you are running a version of PHP before 5.5 and array_column(), you can use the official replacement in plain PHP:
https://github.com/ramsey/array_column
I think this will do what you want
array_uintersect_uassoc
You would have to do something like this
$results = array(
array('id' => 1, 'name' => 'red' , 'spin' => 1),
array('id' => 2, 'name' => 'green', 'spin' => -1),
array('id' => 3, 'name' => 'blue' , 'spin' => .5)
);
$name = array_uintersect_uassoc( $results, array('name' => 'value') , 0, "cmpKey");
print_r($name);
//////////////////////////////////////////////////
// FUNCTIONS
//////////////////////////////////////////////////
function cmpKey($key1, $key2) {
if ($key1 == $key2) {
return 0;
} else {
return -1;
}
}
However, I don't have access to PHP5 so I haven't tested this.
You could do:
$tmp = array_flip($names);
$names = array_keys($tmp);
This is fast function alternative of array_column()
if(!function_exists('array_column')) {
function array_column($element_name) {
$ele = array_map(function($element) {
return $element[$element_name];
}, $a);
return $ele;
}
}
other alternative
function transpose(array $array): array
{
$out = array();
foreach ($array as $rowkey => $row) {
foreach ($row as $colkey => $col) {
$out[$colkey][$rowkey] = $col;
}
}
return $out;
}
function filter_columns(array $arr, string ...$columns): array
{
return array_intersect_key($arr, array_flip($columns));
}
test
$results = array(
array('id' => 1, 'name' => 'red' , 'spin' => 1),
array('id' => 2, 'name' => 'green', 'spin' => -1),
array('id' => 3, 'name' => 'blue' , 'spin' => .5)
);
var_dump(filter_columns(transpose($results),'name'));
var_dump(filter_columns(transpose($results),'id','name'));
var_dump(filter_columns(transpose($results),'id','spin'));

Categories