Converting data from one format to another in PHP - php

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

Related

How to transform nested array to a flat array of nodes PHP?

I have a nested array with a structure as detailed below.
It is an org chart where an employ could have other employees related:
$tree_array = [
[
'id' => 1,
'employee' => 'John',
'leader_id' => NULL,
'team' => [
[
'id' => 2,
'employee' => 'Maria',
'leader_id' => 1,
'team' => [],
],
[
'id' => 3,
'employee' => 'Kevin',
'leader_id' => 1,
'team' => [
[
'id' => 4,
'employee' => 'Alan',
'leader_id' => 3,
'team' => [],
],
[
'id' => 5,
'employee' => 'Bret',
'leader_id' => 3,
'team' => [],
],
],
],
],
]
];
Every node has an ID and a team, that could be an array of other nodes and so on.
I need to obtain each node in a flat array, like this structure:
$flat_array = array(
array(
'id' => 1,
'employee' => 'John',
'leader_id' => NULL
),
array(
'id' => 2,
'employee' => 'Maria',
'leader_id' => 1
),
array(
'id' => 3,
'employee' => 'Kevin',
'leader_id' => 1
),
...
);
I've tried to implement a recursive function, but I get only the nodes of the last iteration.
Any help?
You can make a recursive function, like this:
function flatten($arr, $final=array()) {
foreach($arr as $a) {
$tmp = $a;
unset($tmp['team']);
$final[]= $tmp;
if ($a['team'] && count($a['team']) > 0) $final = flatten($a['team'], $final);
}
return $final;
}
$flat =flatten($tree_array);
Test it here: https://www.tehplayground.com/UHWCccFIkrS5v75Z

Merge/Flatten 3rd level data to create an array of arrays

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?

add key value pair to dynamic multidimensional array in php

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);
*/

Recursive Array Map with Laravel Collection map() Helper

I have a collection of data.
$array = [
[
'id' => 1,
'name' => 'some1',
'type' => 'type1',
'color' => 'color1',
'quantity' => 1
],
[
'id' => 2,
'name' => 'some1',
'type' => 'type1',
'color' => 'color1',
'quantity' => 1
],
[
'id' => 3,
'name' => 'some1',
'type' => 'type1',
'color' => 'color2',
'quantity' => 1
],
[
'id' => 4,
'name' => 'some2',
'type' => 'color1',
'color' => 'type1',
'quantity' => 1
],
......
];
that have different name, type, and color.
I want to group the data by name, type, and color and the result is array data with summary of the same group data.
First, I use this way :
function groupedData($array)
{
$collection = [];
collect($array)->groupBy('name')->map(
function ($item) use (&$collection) {
return $item->groupBy('type')->map(
function ($item) use (&$collection) {
return $item->groupBy('color')->map(
function ($item) use (&$collection) {
$quantity = $item->sum('quantity');
$collection[] = collect($item[0])->merge(compact('quantity'));
}
);
}
);
}
);
return $collection;
}
I expect the output should be like this :
$grouped = [
[
'id' => 1,
'name' => 'some1',
'type' => 'type1',
'color' => 'color1',
'quantity' => 2
],
[
'id' => 2,
'name' => 'some1',
'type' => 'type1',
'color' => 'color2',
'quantity' => 1
],
[
'id' => 3,
'name' => 'some2',
'type' => 'type1',
'color' => 'color1',
'quantity' => 2
],
[
'id' => 4,
'name' => 'some2',
'type' => 'type2',
'color' => 'color1',
'quantity' => 2
],
];
where quantity represent of the number of group items.
But, I the problem is when the required is changed. Just in case :
when user want add other category for grouping, example :
user want to group by name, type, color and size maybe.
Question : How to make a function that can make it more simple and flexible, so doesn't need to change the code when the require changes?
Thanks for the answer.
What you're looking for it sorting, not grouping.
Here's an easy way to do it:
function sort($array, $keys) {
return collect($array)->sortBy(function ($item) use ($keys) {
return array_reduce($keys, function ($carry, $key) use ($item) {
return $carry + $item[$key];
}, '');
})->all();
}
Here's a short explanation:
We're using the collection's sortBy method, which let's us use a callback function that'll return a string to determine the sort order.
We're using array_reduce on the keys to build a single string of all the values in the keys we want to sort by.
Laravel's collection object will use that resulting string to order the collection by.
Finally, we call the all method to get the underlying array from the collection. If you want to actually return a collection, you can remove that last all call.

Recursively Create an Array from another Array

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);

Categories