get parent nodes from a node tree array - php

[
{
"id": 1573695284631,
"name": "Cars",
"pid": 0,
"children": [
{
"id": 1573695292010,
"name": "Audi",
"pid": 1573695284631
},
{
"id": 1573695305619,
"name": "BMW",
"pid": 1573695284631,
"children": [
{
"id": 1573695328137,
"name": "3 Series",
"pid": 1573695305619
},
{
"id": 1573695335102,
"name": "X5",
"pid": 1573695305619
}
]
}
]
},
{
"id": 1573695348647,
"name": "Motorcycles",
"pid": 0,
"children": [
{
"id": 1573695355619,
"name": "Ducatti",
"pid": 1573695348647
}
]
}
]
Suppose I have this node-tree-like array in PHP (represented above in json for readability). For a given child node ID, I would like to find all parent node IDs that it's nested under. For example,
getParentNodes($haystack, $child_node_id=1573695328137); //[1573695284631, 1573695292010, 1573695305619]
I assume this is a use case for recursion. Here's my best attempt:
function getParentNodes($haystack, $child_node_id) {
if( empty($haystack->children) )
return;
foreach($haystack->children as $child) {
if($child->id == $child_node_id) {
// $child found, now recursively get parents
} else {
getParentNodes($child, $child_node_id);
}
}
}

This one will walk the tree until it hits the desired id.
In all cases where the leaf is not the desired one, it will return false - and collapse up the stack resulting in false or an array of parent-ids if the child is found.
Code
function getParentNodes($haystack, $child_node_id) {
foreach ($haystack as $element) {
if ($element['id'] === $child_node_id) {
// return [$element['id']]; // uncomment if you want to include child itself
return [];
} else if (array_key_exists('children', $element)) {
$parentNodes = getParentNodes($element['children'], $child_node_id);
if ($parentNodes !== false) {
return [$element['id'], ...$parentNodes];
}
}
}
return false;
}
Outputs parent ids:
array(2) {
[0]=>
int(1573695284631)
[1]=>
int(1573695305619)
}
Working example.

You missing return result. This is what you want.
function getParentNodes($arr, $child_node_id) {
$result = [];
foreach($arr as $item) {
if($item->pid == $child_node_id) {
$result[] = $item->id;
}
if(!empty($item->children)) {
$result[] = getParentNodes($item->children, $child_node_id);
}
}
return $result;
}
also you need get values as flat array
$values = getParentNodes($values, 1573695284631);
// do flat arr
array_walk_recursive($values,function($v) use (&$result){ $result[] = $v; });
// your values
var_dump($result);
Source reference for flat array

I ended up writing 2 recursive functions
function treeSearch($needle, $haystack) {
foreach($haystack as $node) {
if($node->id == $needle) {
return $node;
} elseif ( isset($node->children) ) {
$result = treeSearch($needle, $node->children);
if ($result !== false){
return $result;
}
}
}
return false;
}
treeSearch will find the node in the tree, then I need to recursively go up the tree until the parent id (pid) is 0
function getParents($node, $hierarchy, $all_parent_ids=[]) {
if($node->pid != 0) {
$parent_node = treeSearch($node->pid, $hierarchy);
$all_parent_ids[] = $parent_node->id;
$result = getParents($parent_node, $hierarchy, $all_parent_ids);
if ($result !== false){
return $result;
}
}
return $all_parent_ids;
}
then, supposing the tree is called $tree I can call them like:
$node = treeSearch(1573695328137, $tree);
$parents = getParents($node, $tree);

This is the best solution to take the parent of a child !
function getPathParent($id, $tree='',$opts='', &$path = array()) {
$in = array_replace(array(
'id'=>'id',
'child'=>'children',
'return'=>'id'
),(array)$opts);
if ( is_array($tree) && !empty($tree) ){
foreach ($tree as $item) {
if ($item[$in['id']] == $id) {
array_push($path, $item[$in['return']]);
return $path;
}
if ( isset($item[$in['child']]) && !empty($item[$in['child']]) ) {
array_push($path, $item[$in['return']]);
if (getPathParent($id, $item[$in['child']],$opts, $path) === false) {
array_pop($path);
} else {
return $path;
}
}
}
}
return false;
}
$tree = [
[
"id" => 1573695284631,
"name" => "Cars",
"pid" => 0,
"children" => [
[
"id" => 1573695292010,
"name" => "Audi",
"pid" => 1573695284631
],
[
"id" => 1573695305619,
"name" => "BMW",
"pid" => 1573695284631,
"children" => [
[
"id" => 1573695328137,
"name" => "3 Series",
"pid" => 1573695305619
],
[
"id" => 1573695335102,
"name" => "X5",
"pid" => 1573695305619
]
]
]
]
],
[
"id" => 1573695348647,
"name" => "Motorcycles",
"pid" => 0,
"children" => [
[
"id" => 1573695355619,
"name" => "Ducatti",
"pid" => 1573695348647
]
]
]
];
$getParentNode = getPathParent(1573695335102,$tree);
var_export($getParentNode);
// return:
/*
array (
0 => 1573695284631,
1 => 1573695305619,
2 => 1573695335102,
)
*/
$getParentNode = getPathParent(1573695335102,$tree,array('return'=>'name'));
var_export($getParentNode);
// return:
/*
array (
0 => 'Cars',
1 => 'BMW',
2 => 'X5',
)
*/
$getParentNode = getPathParent(1573695335102,$tree,array('id'=>'id','child'=>'children','return'=>'pid'));
var_export($getParentNode);
// return:
/*
array (
0 => 0,
1 => 1573695284631,
2 => 1573695305619,
)
*/

Related

PHP merge array by "depth"? [duplicate]

This question already has answers here:
How to array_merge_recursive() an array?
(2 answers)
Closed 3 months ago.
I have an array like this:
[
{
"function_1": {
"element": {
"error": "0",
"msg": "test"
}
}
},
{
"function_1": {
"element_2": {
"error": "0",
"msg": "test"
}
}
},
{
"function_2": {
"element": {
"error": "0",
"msg": "test"
}
}
},
{
"function_2": {
"element_2": {
"error": "0",
"msg": "test"
}
}
}
]
I want output like this:
[
{
"function_1": {
"element": {
"error": "0",
"msg": "test"
},
"element_2": {
"error": "0",
"msg": "test"
}
}
},
{
"function_2": {
"element": {
"error": "0",
"msg": "test"
},
"element_2": {
"error": "0",
"msg": "test"
}
}
}
]
The answers that I found offered to search by name("function_1", "function_2"). But this does not suit me, the function will not always pass an array. I need exactly the "depth" or any other reasonable way.
Thank you!
To achieve your desired result, you could json-decode, recursively merge each individual subarray, then loop over that structure to push each item as a second-level array like this: (Demo)
$array = json_decode($json, true);
$merged = array_merge_recursive(...$array);
$result = [];
foreach ($merged as $key => $data) {
$result[] = [$key => $data];
}
var_export($result);
But I can't imagine getting any benefit from adding unnecessary depth to your result array. I recommend simply json decoding, then calling array_merge_recursive() with the spread operator: (Demo)
var_export(
array_merge_recursive(
...json_decode($json, true)
)
);
Output:
array (
'function_1' =>
array (
'element' =>
array (
'error' => '0',
'msg' => 'test',
),
'element_2' =>
array (
'error' => '0',
'msg' => 'test',
),
),
'function_2' =>
array (
'element' =>
array (
'error' => '0',
'msg' => 'test',
),
'element_2' =>
array (
'error' => '0',
'msg' => 'test',
),
),
)
Your data structure looks weird for the purpose you are trying to achieve I'm bored af tho and created this code for you
function combineElementsPerfunction($functions) {
$result = [];
$uniqueFunctions = [];
foreach ($functions as $function) {
$functionName = array_keys($function)[0];
$uniqueFunctions[] = $functionName;
}
$uniqueFunctions = array_unique($uniqueFunctions);
foreach ($uniqueFunctions as $uniqueFunction) {
$functionObjects = array_filter(
$functions,
function($function) use ($uniqueFunction) {
$functionName = array_keys($function)[0];
return $functionName === $uniqueFunction;
}
);
$elements = [];
foreach ($functionObjects as $functionObject) {
$function = array_shift($functionObject);
$elements = array_merge($elements, $function);
}
$result[] = [
$uniqueFunction => $elements
];
}
return $result;
}
function changeArr($data){
$box = $new = [];
foreach ($data as $v){
$key = array_key_first($v);
$i = count($box);
if(in_array($key, $box)){
$keys = array_flip($box);
$i = $keys[$key];
}else{
$box[] = $key;
}
$new[$i][$key] = isset($new[$i][$key]) ? array_merge($new[$i][$key], $v[$key]) : $v[$key];
}
return $new;
}

Specific problem with traversing an array in php

I have given the array:
array(
"firstName": null,
"lastName": null,
"category": [
"name": null,
"service": [
"foo" => [
"bar" => null
]
]
]
)
that needs to be transform into this:
array(
0 => "firstName",
1 => "lastName",
2 => "category",
"category" => [
0 => "name",
1 => "service",
"service" => [
0 => "foo",
"foo" => [
0 => "bar"
]
]
]
)
The loop should check if a value is an array and if so, it should add the key as a value (0 => category) to the root of array and then leave the key as it is (category => ...) and traverse the value again to build the tree as in example.
I am stuck with this and every time I try, I get wrong results. Is there someone who is array guru and knows how to simply do it?
The code so far:
private $array = [];
private function prepareFields(array $fields):array
{
foreach($fields as $key => $value)
{
if(is_array($value))
{
$this->array[] = $key;
$this->array[$key] = $this->prepareFields($value);
}
else
{
$this->array[] = $key;
}
}
return $this->array;
}
You could make use of array_reduce:
function prepareFields(array $array): array
{
return array_reduce(array_keys($array), function ($result, $key) use ($array) {
$result[] = $key;
if (is_array($array[$key])) {
$result[$key] = prepareFields($array[$key]);
}
return $result;
});
}
Demo: https://3v4l.org/3BfKD
You can do it with this, check the Demo
function array_format(&$array){
$temp_array = [];
foreach($array as $k=>$v){
$temp_array[] = $k;
if(is_array($v)){
array_format($v);
$temp_array[$k] = $v;
}
}
$array = $temp_array;
}
array_format($array);
print_r($array);

how to foreach in inner array

in my relation database,
if id_sub_bidang = 1 then nama_sub_bidang "frontend deveoper".
if id_sub_bidang = 2 then nama_sub_bidang "senior marketing"
my code
$data = Company::find($id);
$result_data = array();
foreach ($data->posting_job as $hasil) {
foreach ($data->sub_bidang as $value) {
$result_data[] = [
'id_sub_bidang' => $hasil->id_sub_bidang,
'nama_sub_bidang' => $value->nama
];
}
}
return response()->json($result_data);
the output
[
{
"id_sub_bidang": 1,
"nama_sub_bidang": "Frontend Developer"
},
{
"id_sub_bidang": 1,
"nama_sub_bidang": "Senior Marketing"
},
{
"id_sub_bidang": 2,
"nama_sub_bidang": "Frontend Developer"
},
{
"id_sub_bidang": 2,
"nama_sub_bidang": "Senior Marketing"
}
]
expected result
[
{
"id_sub_bidang": 1,
"nama_sub_bidang": "Frontend Developer"
},
{
"id_sub_bidang": 2,
"nama_sub_bidang": "Senior Marketing"
}
]
i want to loop in inner array but doesnt work. so, i use this way.
what the problem ?
Change your foreach loops to this:
foreach ($data->posting_job as $hasil) {
foreach ( $hasil->id_sub_bidang as $hasilid) {
$result_data[] = ['id_sub_bidang' => $hasil->id_sub_bidang];
foreach ($data->sub_bidang as $value) {
$result_data[] = ['nama_sub_bidang' => $value->nama];
}
}
}
Build an array then using the method recursive to clean the array by duplicate entry.
array_replace_recursive
Another solution
$empty_stats = Array(
'id_sub_bidang' => null,
'nama_sub_bidang' => null
);
foreach ($array as $value) {
if (!array_key_exists($id_sub_bidang, $array)) {
$array[] = $empty_stats;
}
$array[] = [
'id_sub_bidang' => $hasil->id_sub_bidang,
'nama_sub_bidang' => $value->nama
];
}

Get parent values from multidimensional associative json file

I have a multi- and variable-level json file that looks like the example at the bottom of this post. What I need to do is search for a particular value, let's say "Gap junction" and return the value for "name" for all of the higher level parents, in this example "Junction", "Plasma membrane" and "Cell". I need to do this using php and I think I need a recursive loop to traverse the array that will record the "name" value for each depth level into an array and then return this "name" array once the search term is found, but I'm struggling a bit at achieving this. Any help would be appreciated.
{
"name": "Cell",
"children": [
{
"name": "Plasma membrane",
"children": [
{
"name": "Junction",
"children": [
{"name": "Adherens junction"},
{"name": "Caveola"},
{"name": "Gap junction"},
{"name": "Lipid raft"},
{"name": "Tight junction"}
]
},
{"name": "Focal adhesion"}
]
},
{
"name": "Vesicle",
"children": [
{
"name": "Endosome",
"children": [
{"name": "Early Endosome"},
{"name": "Late Endosome"},
{"name": "Recyling Endosome"}
]
},
{ "name": "Microsome"}
]
}
]
}
EDIT
Current code as requested. The $found variable is certainly not working how I intended it too. Code is based/modified on this answer: Get Parent and Child depth level from JSON using PHP?
$jsonString = file_get_contents("./information/localization.json");
$jsonArray = json_decode($jsonString);
$currOrganelle = "Gap junction";
$parents = read_tree_recursively($jsonArray, $currOrganelle);
function read_tree_recursively($items, $searchTerm, $result = array(), $level = 0, $found = false) {
foreach($items as $child) {
if(!$found) {
$currName = $child->name;
if($currName == $searchTerm) {
$found = true;
return $result;
}
elseif(!empty($child->children)) {
$result[$level] = $currName;
$result = read_tree_recursively($child->children, $searchTerm, $result, $level + 1, $found);
if($found) return $result;
}
else {
}
}
else {
return $result;
}
}
}
Solution with RecursiveIteratorIterator and RecursiveArrayIterator classes:
// $str - is your initial json string
$decoded = json_decode($str, TRUE);
function getParentNameKeys($arr = [], $needle = "") {
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($arr));
$nameKeys = [];
foreach ($iterator as $key => $value) {
if ($value === $needle) {
$depth = $iterator->getDepth();
while ($depth--){
if ($iterator->getSubIterator($depth)->offsetExists('name')) {
$nameKeys[] = $iterator->getSubIterator($depth)->offsetGet('name');
}
}
}
}
return $nameKeys;
}
$nameKeys = getParentNameKeys($decoded, "Gap junction");
var_dump($nameKeys);
// the output:
array (size=3)
0 => string 'Junction' (length=8)
1 => string 'Plasma membrane' (length=15)
2 => string 'Cell' (length=4)
$nameKeys = getParentNameKeys($decoded, "Early Endosome");
var_dump($nameKeys);
// the output:
array (size=3)
0 => string 'Endosome' (length=8)
1 => string 'Vesicle' (length=7)
2 => string 'Cell' (length=4)
http://php.net/manual/en/class.recursiveiteratoriterator.php

Recursivly search array and sum field

I am trying to write a recursive function that will iterate over an array of arrays and sum a specific field. Here is an example of an array:
{
"68": {
"10": [
{
"id": "3333",
"sumTHis": "5"
}
]
},
"69": {
"45": [
{
"id": "3333",
"sumTHis": "5"
}
],
"50": [
{
"id": "3330",
"sumTHis": "5"
},
{
"id": "3331",
"sumTHis": "5"
},
{
"id": "3332",
"sumTHis": "5"
},
{
"id": "3333",
"sumTHis": "5"
}
]
}
}
The problem is that the array could be any number of sub-arrays deep. In the end, I would like to be able to sum all "sumTHis" nodes throughout the entire array The code I have so far is this:
//in body
$sumThis= recurse_get_total($array, 'sumTHis');
//recursive function
function recurse_get_total($report_data, $valId, $total = 0){
try{
foreach ($report_data as $key => $value) {
if(is_array_of_arrays($value)){
recurse_get_total($value, $valId, $total);
}else{
$total = $total + $value[$valId];
return $total;
}
}
return $total;
}catch(Exception $err){
throw $err;
}
}
function is_array_of_arrays($isArray){
try{
if(is_array($isArray)){
foreach($isArray as $key => $value){
if(!is_array($value)){
return false;
}
}
return true;
}
}catch(Exception $err){
throw $err;
}
}
This function starts to iterate over the array but gets kicked out after the first one and returns 0. Can anyone help out?
Thanks
jason
Going about this problem I set something up with "array_walk_recursive". Seeing that you want to add some stuff independent of the depth of the arrays, this seems to work.
It is not solving it with what you have, but perhaps this different approach will get you there.
$sum = 0;
$array = array(
"one" => array(
"day" => "tuesday",
"week" => "20",
"findthis" => 10
),
"two" => array("subone" => array(
"some" => "one",
"findthis" => 23
)),
"deeperthree" => array("subtwo" => array("deeper" => array(
"one" => "entry",
"findthis" => 44
)))
);
function callback($val, $key, $arg) {
if ($key == "findthis") {
$arg[0]($val, $arg[1]);
}
};
$function = function($num, &$sum) {
$sum = $sum + $num;
echo $sum . " ";
};
array_walk_recursive($array, "callback", array( $function, &$sum ));
result: 10 33 77

Categories