Having this array :
[
"id" => 5,
"name" => "Item 5",
"all_parents" => [
"id" => 4,
"name" => "Item 4",
"all_parents" => [
"id" => 3,
"name" => "Item 3",
"all_parents" => [
"id" => 2,
"name" => "Item 2",
"all_parents" => [
"id" => 1,
"name" => "Item 1",
"all_parents" => null
]
]
]
]
]
I created a recursive php function that transform that array to this:
[
["id" => 1, "name" => "Item 1"],
["id" => 2, "name" => "Item 2"],
["id" => 3, "name" => "Item 3"],
["id" => 4, "name" => "Item 4"],
["id" => 5, "name" => "Item 5"],
]
The code is this:
private array $breadcrumb = [];
private function generateBreadcrumb($structure) : array
{
if($structure) {
$this->breadcrumb[] = array(
"id" => $structure['id'],
"name" => $structure['name'],
);
$this->generateBreadcrumb($structure['all_parents'] ?? []);
}
return array_reverse($this->breadcrumb);
}
How can I redesign this method without depending on class property $breadcrumb?
By following your initial code, you could do:
function generateBreadcrumb($structure, &$output = []) : array
{
if ($structure) {
$output[] = array(
"id" => $structure['id'],
"name" => $structure['name'],
);
$this->generateBreadcrumb($structure['all_parents'] ?? [], $output);
}
return array_reverse($output);
}
However it could be improved, at least by avoiding to call array_reverse() each time, but only for the root call.
Instead of implementing a recursive function there is the possibility of using the built-in array_walk_recursive function:
$arr = [
'id' => 5,
'name' => 'Item 5',
'all_parents' => [
'id' => 4,
'name' => 'Item 4',
'all_parents' => [
'id' => 3,
'name' => 'Item 3',
'all_parents' => [
'id' => 2,
'name' => 'Item 2',
'all_parents' => [
'id' => 1,
'name' => 'Item 1',
'all_parents' => null
]
]
]
]
];
function generateBreadcrumb($structure): array {
$retval = [];
array_walk_recursive($structure, function ($item, $key) use (&$retval) {
if ($key === 'id') {
$retval[] = [$key => $item];
} elseif ($key === 'name') {
$retval[array_key_last($retval)][$key] = $item;
}
});
return array_reverse($retval);
}
$result = generateBreadcrumb($arr);
Note that array_walk_recursive only visits leafs, so with the exception of the innermost 'all_parents', the other ones are not visited.
A none-recursive version would be this:
function generateBreadcrumb(array $arr): array {
$retval = [];
$temp = &$arr;
do {
$retval[] = [ 'id' => $temp['id'], 'name' => $temp['name'] ];
$temp = &$temp['all_parents'];
} while ($temp !== null);
return array_reverse($retval);
}
You can accumulate the indeterminate-depth data by merging as you recurse the tree. You do not need to introduce any new variables to carry the data while recursing nor do you need to array_reverse() the returned data.
The below technique will prioritize recursion while $structure['all_parents'] is truthy (not null) and cease recursion once it encounters the null all_parents value in the deepest subarray. From the bottom, the id and name elements will be accessed and merged into the empty or accumulated array of row data.
Code: (Demo)
class Recursing
{
public function generateBreadcrumb(array $structure): array
{
return array_merge(
$structure['all_parents']
? $this->generateBreadcrumb($structure['all_parents'])
: [],
[
['id' => $structure['id'], 'name' => $structure['name']]
]
);
}
}
$test = new Recursing;
var_export($test->generateBreadcrumb($arr));
Output:
array (
0 =>
array (
'id' => 1,
'name' => 'Item 1',
),
1 =>
array (
'id' => 2,
'name' => 'Item 2',
),
2 =>
array (
'id' => 3,
'name' => 'Item 3',
),
3 =>
array (
'id' => 4,
'name' => 'Item 4',
),
4 =>
array (
'id' => 5,
'name' => 'Item 5',
),
)
Related
I have an array that contains data of incoming mail, looks something like this:
$incomingMail = [
0 => [
'title' => 'Title 1',
'areaCode' => 101
],
1 => [
'title' => 'Title 2',
'areaCode' => 101
],
2 => [
'title' => 'Title 3',
'areaCode' => 102
]
];
And another array containing area name and code, the array looks like this:
$areaArr = [
0 => [
'name' => 'Area 1',
'code' => 101
],
1 => [
'name' => 'Area 2',
'code' => 102
],
2 => [
'name' => 'Area 3',
'code' => 103
],
3 => [
'name' => 'Area 4',
'code' => 104
]
];
I want to create an array that contains the count of incomingMail array based on areaArr's code, it will kind of looks like this:
$areaWithMailCount = [
0 => [
'areaName' => 'Area 1',
'areaCode' => 101,
'mailCount' => 2
],
1 => [
'areaName' => 'Area 2',
'areaCode' => 102,
'mailCount' => 1
],
2 => [
'areaName' => 'Area 3',
'areaCode' => 103,
'mailCount' => 0
],
3 => [
'areaName' => 'Area 4',
'areaCode' => 104,
'mailCount' => 0
]
];
I have tried to loop those arrays and add the condition based on area code but the result isn't quite what I wanted, the code looks something like this:
$areaWithMailCount = [];
foreach($areaArr as $area) {
foreach($incomingMail as $mail) {
if($mail['areaCode'] == $area['code']) {
$areaWithMailCount[] = [
'areaName' => $area['name'],
'areaCode' => $area['code'],
'mailCount' => count($mail)
];
}
}
}
The result from above code is like this:
[
0 => [
"areaName" => "Area 1"
"areaCode" => 101
"mailCount" => 2
],
1 => [
"areaName" => "Area 1"
"areaCode" => 101
"mailCount" => 2
],
2 => [
"areaName" => "Area 2"
"areaCode" => 102
"mailCount" => 2
]
];
Any ideas how to achieve that?
I know this has been answered but just to add a bit of a cleaner way.
This is essentially using the same process as https://stackoverflow.com/a/75443993/1791606 but is too big for a comment so I've stuck it in an answer.
$mailCounts = array_count_values(array_column($incomingMail, 'areaCode'));
$areaWithMailCount = array_map(fn(array $area) => [
'areaName' => $area['name'],
'areaCode' => $area['code'],
'mailCount' => $mailCounts[$area['code']] ?? 0,
], $areaArr);
It is inefficient to use nested loops -- this will make your server do loads of unnecessary cycles while searching for qualifying values.
Instead, loop the $incomingMail array only once and populate a lookup array which the $areaArr array can reference as it is iterated. If the code in the $areaArr does not related to any keys in the lookup array, then default the value to 0.
Code: (Demo)
$lookup = [];
foreach ($incomingMail as $row) {
$lookup[$row['areaCode']] = ($lookup[$row['areaCode']] ?? 0) + 1;
}
$result = [];
foreach ($areaArr as $row) {
$result[] = [
'areaName' => $row['name'],
'areaCode' => $row['code'],
'mailCount' => $lookup[$row['code']] ?? 0
];
}
var_export($result);
The functional-style equivalent of the above 2-loop snippet looks like this: Demo
$lookup = array_reduce(
$incomingMail,
function($result, $row) {
$result[$row['areaCode']] = ($result[$row['areaCode']] ?? 0) + 1;
return $result;
}
);
var_export(
array_map(
fn($row) =>
[
'areaName' => $row['name'],
'areaCode' => $row['code'],
'mailCount' => $lookup[$row['code']] ?? 0
],
$areaArr
)
);
Or even: Demo
var_export(
array_map(
function($row) use ($incomingMail) {
static $lookup;
$lookup ??= array_reduce(
$incomingMail,
function($result, $row) {
$result[$row['areaCode']] = ($result[$row['areaCode']] ?? 0) + 1;
return $result;
}
);
return [
'areaName' => $row['name'],
'areaCode' => $row['code'],
'mailCount' => $lookup[$row['code']] ?? 0
];
},
$areaArr
)
);
Is there a way to mutate an array using array_reduce in PHP?
I'm trying to do something like this:
Given some ordered list of ids:
$array = [["id" => 1], ["id" => 13], ["id" => 4]];
And a tree that has a subtree matching the corresponding ids:
$tree = [
"id" => 2334,
"children" => [
[
"id" => 111,
"children" => []
],
[
"id" => 1, // <- this is a match
"children" => [
[
"id" => 13, // <- this is a match
"children" => [
[
"id" => 4, // <- this is a match
"children" => []
],
[
"id" => 225893,
"children" => []
],
[
"id" => 225902,
"children" => []
]
]
]
]
]
]
];
How can I mutate the arrays in that subtree?
I'm currently trying to use array_reduce to walk down the tree and mutate it. However, the mutation isn't being applied to the originally passed in $tree.
array_reduce($array, function (&$acc, $item) {
$index = array_search($item['id'], array_column($acc['children'], 'id'));
$acc['children'][$index]['mutated'] = true; // mutation here
return $acc['children'][$index];
}, $tree);
echo "<pre>";
var_dump($tree); // $tree is unchanged here
echo "</pre>";
Why is $tree not mutated after the running above array_reduce?
Is there a way to use foreach in this case?
I think this function will do what you want. It recurses down $tree, looking for id values that are in $array and setting the mutation flag for those children:
function mutate(&$tree, $array) {
if (in_array($tree['id'], array_column($array, 'id'))) {
$tree['mutated'] = true;
}
foreach ($tree['children'] as &$child) {
mutate($child, $array);
}
}
mutate($tree, $array);
var_export($tree);
Output:
array (
'id' => 2334,
'children' => array (
0 => array (
'id' => 111,
'children' => array ( ),
),
1 => array (
'id' => 1,
'children' => array (
0 => array (
'id' => 13,
'children' => array (
0 => array (
'id' => 4,
'children' => array ( ),
'mutated' => true,
),
1 => array (
'id' => 225893,
'children' => array ( ),
),
2 => array (
'id' => 225902,
'children' => array ( ),
),
),
'mutated' => true,
),
),
'mutated' => true,
),
),
)
Demo on 3v4l.org
How to get particular nested array based on the given matched key using PHP built in function
Scenario
$id = 1035; // Searching ID
$a = [
'id'=> 291,
'children' => [
[
'id' => 1034,
'children' => [
[
'id' => 111,
'name' => 'ABC',
'figure' => '6 digits',
'children'=> []
],
[
'id' => 1035,
'lft' => 'LEFT',
'children' => [
[
'id' => 1036,
'children' => [
[
'id' => 222,
'someKey' => 'some value',
'children'=> []
]
]
],
[
'id' => 333,
'someKey' => 'some value',
'children'=> []
]
],
]
],
],
[
'id' => 1024,
'title' => 'ABC',
'children' => [
],
]
]
];
Please note, 'id' & 'children' keys are always be there. How to get the "children" of "1035" ID..?
Expected Output
[
[
'id' => 1036,
'children' => [
[
'id' => 222,
'someKey' => 'some value',
'children'=> []
]
],
],
[
'id' => 333,
'someKey' => 'some value',
'children'=> []
]
];
Tried
function getRecursiveCategoryIds($key, $categories = []){
$return = null;
try {
array_walk_recursive($categories, function($v, $k) use ($key, &$return){
if (null != $return) {
// Run loop to get the next immediate "children" key
if ($k == 'children') { // It's not matching anymore
$return = $v;
//return false;
throw new Exception;
}
} else if($v == $key) {
// Found
$return = $v;
}
});
} catch(Exception $e) {}
return $return;
}
$d = getRecursiveCategoryIds($id, $a);
echo '<pre>D: '; print_r($d); die;
I tried by the above code, but the "if ($k == 'children') {" is not matched any more..!
Any suggestions are welcome... (PHP's Built in function is most prefer!)
I was able to do this. Please check the comments in the code:
<?php
$id = 1035; // Searching ID
$myObj = array();
$a = [
'id'=> 291,
'children' => [
[
'id' => 1034,
'children' => [
[
'id' => 111,
'name' => 'ABC',
'figure' => '6 digits',
'children'=> []
],
[
'id' => 1035,
'lft' => 'LEFT',
'children' => [
[
'id' => 1036,
'children' => [
[
'id' => 222,
'someKey' => 'some value',
'children'=> []
]
]
],
[
'id' => 333,
'someKey' => 'some value',
'children'=> []
]
],
]
],
],
[
'id' => 1024,
'title' => 'ABC',
'children' => [
],
]
]
];
function findObject($id, $obj) {
global $myObj;
// This is an object.
if (isset($obj["id"])) {
echo "Checking {$obj["id"]}<br />";
// Check the id to what we need.
if ($obj["id"] == $id) {
// Yay! We found it. Return the object.
echo "Yay we found {$obj["id"]}<br />";
$myObj = $obj;
}
else {
echo "Checking children of {$obj["id"]}<br />";
// See if it has any children
if (isset($obj["children"]) && count($obj["children"]) > 0) {
echo "There are children for {$obj["id"]}<br />";
foreach ($obj["children"] as $child) {
findObject($id, $child);
}
}
}
}
}
findObject($id, $a);
print_r($myObj);
Output
Checking 291Checking children of 291There are children for 291Checking 1034Checking children of 1034There are children for 1034Checking 111Checking children of 111Checking 1035Yay we found 1035Need to find a way to break out!Checking 1024Checking children of 1024Found it!Array
(
[id] => 1035
[lft] => LEFT
[children] => Array
(
[0] => Array
(
[id] => 1036
[children] => Array
(
[0] => Array
(
[id] => 222
[someKey] => some value
[children] => Array
(
)
)
)
)
[1] => Array
(
[id] => 333
[someKey] => some value
[children] => Array
(
)
)
)
)
Demo:
https://ideone.com/UoKqrU
https://3v4l.org/rWkPq
You can use function inside other check :
$id=1035;
$a = [
'id'=> 291,
'children' => [
[
'id' => 1034,
'children' => [
[
'id' => 111,
'name' => 'ABC',
'figure' => '6 digits',
'children'=> []
],
[
'id' => 1035,
'lft' => 'LEFT',
'children' => [
[
'id' => 1036,
'children' => [
[
'id' => 222,
'someKey' => 'some value',
'children'=> []
]
]
],
[
'id' => 333,
'someKey' => 'some value',
'children'=> []
]
],
]
],
],
[
'id' => 1024,
'title' => 'ABC',
'children' => [
],
]
]
];
function nigsearch($arr,$id)
{
if(gettype($arr) == 'array')
foreach($arr as $key =>$list)
{
if(gettype($list) == 'array'){
if(isset($list['id']))
{
if($list['id'] ==$id)
print_r($list['children']);
}
nigsearch($list,$id);
}
}
}
foreach($a as $key)
{
nigsearch($key,$id);
}
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);
So I have this array in PHP.
$arr = [
[ 'sections' => [1], 'id' => 1 ],
[ 'sections' => [2], 'id' => 1 ],
[ 'sections' => [3], 'id' => NULL ],
[ 'sections' => [4], 'id' => 4 ],
[ 'sections' => [5], 'id' => 4 ],
[ 'sections' => [6], 'id' => 4 ]
];
I want to merge on 'id' and get something like
$arr = [
[ 'sections' => [1, 2], 'id' => 1 ],
[ 'sections' => [3], 'id' => NULL ],
[ 'sections' => [4, 5, 6], 'id' => 4 ]
];
Just struggling to get my head around this one. Any Ideas
I've created this quick function that might work for you
<?php
// Your array
$arr = array(
array( 'elem1' => 1, 'elem2' => 1 ),
array( 'elem1' => 2, 'elem2' => 1 ),
array( 'elem1' => 3, 'elem2' => NULL ),
array( 'elem1' => 4, 'elem2' => 4 ),
array( 'elem1' => 5, 'elem2' => 4 ),
array( 'elem1' => 6, 'elem2' => 4 )
);
print_r($arr);
function mergeBy($arr, $elem2 = 'elem2') {
$result = array();
foreach ($arr as $item) {
if (empty($result[$item[$elem2]])) {
// for new items (elem2), just add it in with index of elem2's value to start
$result[$item[$elem2]] = $item;
} else {
// for non-new items (elem2) merge any other values (elem1)
foreach ($item as $key => $val) {
if ($key != $elem2) {
// cast elem1's as arrays, just incase you were lazy like me in the declaration of the array
$result[$item[$elem2]][$key] = $result[$item[$elem2]][$key] = array_merge((array)$result[$item[$elem2]][$key],(array)$val);
}
}
}
}
// strip out the keys so that you dont have the elem2's values all over the place
return array_values($result);
}
print_r(mergeBy($arr));
?>
Hopefully it'll work for more than 2 elements, and you can choose what to sort on also....