I have a multidimensional array as per below example which I want to search through for a specific value $needle. If this value is found I want to add a new key=> value pair for each node of the array branch where the $needle is subsequently found.
The needle should be unique but the depth of the sub arrays is dynamic.
Example:
$data= [
0=> [
'title' => 'alpha',
'children' => [],
],
1 =>[
'title' => 'beta',
'children' => [
0=>[
'title' => 'charlie',
'children' => [],
],
1=>[
'title' => 'delta',
'children' => [],
],
2=>[
'title' => 'echo',
'children' => [
0=>[
'title' => 'foxtrot',
'children' => [],
],
],
],
],
],
2 => [
'title' => 'gulf',
'children' => []
]
Now when I search for $needle= foxtrot I want to add to each of the subarrays "active" => 1
e.:
$data= [
0=> ...
1 =>[
'title' => 'beta',
**'active' => 1,**
'children' => [
0=>[
'title' => 'charlie',
'children' => [],
],
1=>[
'title' => 'delta',
'children' => [],
],
2=>[
'title' => 'echo',
**'active' => 1,**
'children' => [
0=>[
'title' => 'foxtrot',
**'active' => 1,**
'children' => [],
],
],
],
],
],
My not working attempt. I don't know how I correctly merge the key => value on each finding:
function array_mod_active($array, $needle, $parent_key='' )
{
foreach($array as $key => $value) {
if (is_array($value)) {
array_mod_active($value, $needle, $key );
} elseif ($value === $needle) {
array_merge(array('active' => 1), $array[$parent_key]);
}
}
return $array;
}
This is one recursive way to do it. I've added inline comments to explain each step.
Code: (Demo)
function keysort($a,$b){ // this function is optional; purely used for aesthetic/readability purposes
$order=['title'=>0,'active'=>1,'children'=>2]; // order keys by this specific order
return $order[$a]<=>$order[$b];
}
function activate_lineage(&$array,$needle){
foreach($array as &$item){
if($item['title']==$needle){ // $needle located, make active
$item['active']=1; // add active element
uksort($item,'keysort'); // optional call
return $array; // send full updated array back where it came from
}elseif(!empty($item['children'])){ // drill down if there are any children
$copy=$item; // preserve original (this avoids a post-recursion column-search)
$item['children']=activate_lineage($item['children'],$needle); // recurse
if($copy!==$item){ // if children subarray was updated, a child was activated
$item['active']=1; // add active element
uksort($item,'keysort'); // optional call
break; // don't continue the loop; only one parent will be activated (per level)
}
}
}
return $array; // return the full array, no matter what level it is on
}
// to call:
var_export(activate_lineage($data,'foxtrot'));
/* or because the function modifies by reference...
activate_lineage($data,'foxtrot');
var_export($data);
*/
Related
Is it possible to search a multidimensional array of unknown depth by value?
For example, with:
$data = [
[
'uid' => '100',
'name' => 'MAIN',
[
'uid' => '2222',
'name' => 'SUB_MAIN',
[
'uid' => '8524',
'name' => 'SUB_SUB_MAIN',
]
]
],
[
'uid' => '5465',
'name' => 'MAIN',
],
[
'uid' => '40489',
'name' => 'MAIN',
]
];
I want to find the path to the sub-array where uid is 8524.
With the above array, the result should be: [0, 0, 0].
You can use the following as a starting point:
<?php
declare(strict_types=1);
error_reporting(-1);
ini_set('display_errors', 'On');
function findPath(array $items, callable $criteria, array $path = []): array {
foreach ($items as $key => $item) {
if (!is_array($item)) {
continue;
}
// prepare path to this item
$pathToHere = array_merge($path, [$key]);
// if the items fits the criteria
if ($criteria($item)) {
// return it's path
return $pathToHere;
}
// otherwise check children
$pathToChild = findPath($item, $criteria, $pathToHere);
// and if return value is not empty
if (count($pathToChild) > 0) {
// return path to child
return $pathToChild;
}
}
// base case if no item matches
return [];
}
$data = [
[
'uid' => '5465',
'name' => 'MAIN',
],
[
'uid' => '100',
'name' => 'MAIN',
[
'uid' => '2222',
'name' => 'SUB_MAIN',
[
'uid' => '8524',
'name' => 'SUB_SUB_MAIN',
[
'uid' => 'X',
'name' => 'Y',
]
],
[
'uid' => '8524_test',
'name' => 'SUB_SUB_MAIN_test',
[
'uid' => '8524_test_sub',
'name' => 'SUB_SUB_MAIN_test_sub',
]
]
]
],
[
'uid' => '40489',
'name' => 'MAIN',
]
];
$path = findPath($data, fn(array $item): bool => $item['uid'] === '8524_test_sub');
print_r($path);
/*
Array
(
[0] => 1
[1] => 0
[2] => 1
[3] => 0
)
*/
Demo: https://3v4l.org/XKTH8
Note the code above requires php 7.4, but only because of the fn() => ... construct. This could simply be replaced by any other suitable callable.
I have an array with 3 levels and I'd like to merge/flatten all 3rd level subarrays into one subarray/row on the 2nd level.
$array = [
[
'User' => [
'id' => 57341,
'updated' => null,
'userId' => 57341,
'value' => null,
'lat' => 53.4537812,
'lon' => -2.1792437,
],
[
'feed_likes' => 'NA',
'category_idx' => -1,
'type' => 'User'
]
],
[
'User' => [
'id' => 57336,
'updated' => null,
'userId' => 57336,
'value' => null,
'lat' => 53.473684,
'lon' => -2.2399827,
],
[
'feed_likes' => 'NA',
'category_idx' => -1,
'type' => 'User'
]
],
];
The deep User-keyed subarrays (having 6 elements) should be merged with its sibling/indexed subarray (having 3 elements) to form a 9-element row on the second level.
Desired result:
[
[
'id' => 57341,
'updated' => null,
'userId' => 57341,
'value' => null,
'lat' => 53.4537812,
'lon' => -2.1792437,
'feed_likes' => 'NA',
'category_idx' => -1,
'type' => 'User'
],
[
'id' => 57336,
'updated' => null,
'userId' => 57336,
'value' => null,
'lat' => 53.473684,
'lon' => -2.2399827,
'feed_likes' => 'NA',
'category_idx' => -1,
'type' => 'User'
]
]
You can use splat ... operator with array_merge
foreach($a as $child){
$flatten[] = array_merge(...$child);
}
Working example :- https://3v4l.org/HkUh6
To merge and flatten the 3rd level data sets into consolidated 2nd level rows with functional style programming, make iterated calls of array_merge() which receive all 3rd level payloads at once. The spread operator (...) is a concise technique used to unpack multiple elements in an array. A special consideration is needed for this case because spreading elements which have non-numeric keys will cause code breakage. To overcome this, simply call array_values() to "index" the array (replace all keys with sequenial numbers) before spreading.
Code: (Demo)
var_export(
array_map(
fn($rows) => array_merge(...array_values($rows)),
$array
)
);
Slightly different to your example output, but you can merge your inner arrays.
<?php
$data =
[
[
'animal' => [
'type' => 'fox'
],
[
'colour' => 'orange'
]
],
[
'animal' => [
'type' => 'panda'
],
[
'colour' => 'black and white'
]
]
];
$result =
array_map(
function($v) {
return array_merge($v['animal'], $v[0]);
},
$data
);
var_export($result);
Output:
array (
0 =>
array (
'type' => 'fox',
'colour' => 'orange',
),
1 =>
array (
'type' => 'panda',
'colour' => 'black and white',
),
)
If I understood you correctly you need to merge User array and in this case with the second array in that key. it that case something like this should work
foreach($array as $key=>$deep1){
$newArray = [];
foreach($deep1 as $deep2){
$newArray = array_merge($newArray,$deep2)
}
$array[$key] = $newArray;
}
Did II understand your question correctly?
I am trying to convert this tree structure:
{
"id":1,"name":"Corp.","emoji":"🐕","parent_id":null,"children":
[
{"id":2,"name":"Food","emoji":"🥩","parent_id":1,"children":[]},
{"id":3,"name":"Canine+Therapy","emoji":"😌","parent_id":1,"children":
[
{"id":4,"name":"Massages","emoji":"💆","parent_id":3,"children":[]},
{"id":5,"name":"Games","emoji":"🎾","parent_id":3,"children":[]}
]
}
]
}
to this format:
[
[ 'id' => 1, 'name' => 'Corp.', 'emoji' => '🐕' ,'parent_id' => null ],
[ 'id' => 2, 'name' => 'Food', 'emoji' => '🥩', 'parent_id' => 1 ],
[ 'id' => 3, 'name' => 'Canine Therapy', 'emoji' => '😌', 'parent_id' => 1 ],
[ 'id' => 4, 'name' => 'Massages', 'emoji' => '💆', 'parent_id' => 3 ],
[ 'id' => 5, 'name' => 'Games', 'emoji' => '🎾', 'parent_id' => 3 ],
]
Any idea on how to approach this?
Thanks
You can use a recursive function like this:
function collapse($tree, &$collapsed = []) {
$tree = clone $tree;
$collapsed[] = $tree;
foreach ($tree->children as $child) {
collapse($child, $collapsed);
}
unset($tree->children);
return $collapsed;
}
$new = collapse($tree);
Note that the $collapsed variable that holds the result must be passed by reference for this to work. Each time the function is called the current node is added to the result array, then each of its children is added recursively, then the children property is removed.
clone is necessary in the first line to prevent this function from destroying the original tree when it unsets children. If you don't need the original tree you can omit that.
Working example: https://3v4l.org/U6mHs
I've seen many questions around this topic, but not any near the case I have.
I have like a really simple folder path as key and want to make the array into a multidimensional array.
My current array
[
'projects' => 'A path',
'projects/project-a' => 'Another path',
'projects/project-b' => 'Yet another path',
'about/me/and/someone/else' => 'Path about me'
]
This is the result I try to get:
[
'projects' => [
'path' => 'A path',
'children' => [
'project-a' => [
'path' => 'Another path'
],
'project-b' => [
'path' => 'Yet another path'
]
]
],
'about' => [
'children' => [
'me' => [
'children' => [
'and' => [
'children' => [
'someone' => [
'children' => [
'else' => [
'path' => 'Path about me'
]
]
]
]
]
]
]
]
]
]
Maybe I can use array_walk_recursive somehow. I know explode can be used to split the parts by /.
Notes
projects/project-a does not have children.
about and all children except the last one does not have a path.
The depth of the array is unknown.
$result = [];
foreach($arr as $k=>$v) {
$path = explode('/', $k);
// temporary array for one path
$temp = [];
// Pointer, used to add a next level
$p = &$temp;
// Save the last part of path
$last = array_pop($path);
foreach($path as $s) {
// Make level upto the last
$p[$s] = ['children' => []];
$p = &$p[$s]['children'];
}
// Add a value
$p[$last] = ['path' => $v];
$result = array_merge_recursive($result, $temp);
}
print_r($result);
demo
I am trying to make a multi-dimensional array build an array path adding the hr field so it looks like this:
I just can't figure out how to add the totals, nor create a sub-array so the dot notation in an option too. My goal is to get something like this:
[1] => [1][2][1][5][0][6] = 35 (the second child path "1")
[1] => [1][2][1][5][0][7] = 25
or Something like this:
array (
[children.0.children.0.children.0.total] = 20
[children.0.children.1.children.1.total] = 35
// etc
)
The complicated part is that it goes in different directions and I want to know what is the highest and lowest total based on the path:
==> Run Code Here or Copy/Paste
// -------------
// The Flattener
// -------------
function doit($myArray) {
$iter = new RecursiveIteratorIterator(new RecursiveArrayIterator($myArray));
$result = array();
foreach ($iter as $leafKey => $leafValue) {
$keys = array();
foreach (range(0, $iter->getDepth()) as $depth) {
$keys[] = $iter->getSubIterator($depth)->key();
}
$result[ join('.', $keys) ] = $leafValue;
}
return $result;
}
// -------------
// Example Tree
// -------------
$tree = [
'id' => 1,
'type' => 'note',
'data' => [],
'children' => [
[
'id' => 2,
'type' => 'wait',
'data' => [
'hr' => 10,
],
'children' => [
[
'id' => 3,
'type' => 'wait',
'data' => [
'hr' => 10,
],
'children' => [
'id' => 4,
'type' => 'exit',
'data' => [],
'children' => []
]
],
[
'id' => 5,
'type' => 'note',
'data' => [
'hr' => 10,
],
'children' => [
[
'id' => 6,
'type' => 'wait',
'data' => [
'hr' => 10,
],
'children' => []
],
[
'id' => 7,
'type' => 'exit',
'data' => [],
'children' => []
],
]
]
],
]
]
];
$result = doit($tree);
print_r($result);
This seems to work, I found it somewhere googling all day.
array_reduce(array_reverse($keys), function($parent_array, $key) {
return $parent_array ? [$key => $parent_array] : [$key];
}, null);