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
Related
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;
}
This question already has answers here:
How to GROUP BY and SUM PHP Array? [duplicate]
(2 answers)
Closed 9 months ago.
I'm having a hard time manipulating an array of objects in PHP. I need to group the objects by id, while summing up the points.
Starting array of objects:
[
{
"id": "xx",
"points": 25
},
{
"id": "xx",
"points": 40
},
{
"id": "xy",
"points": 40
},
]
What I need:
[
{
"id": "xx",
"points": 65
},
{
"id": "xy",
"points": 40
},
]
As a frontender, I'm having a hard time with object/array manipulations in PHP. Any help would be greatly appreciated!
i hope this answer help you
first i will change objects to array and return the result to array again
$values =[
[
"id"=> "xx",
"points"=> 25
],
[
"id"=> "xx",
"points"=> 40
],
[
"id"=> "xy",
"points"=> 40
],
];
$res = array();
foreach($values as $vals){
if(array_key_exists($vals['id'],$res)){
$res[$vals['id']]['points'] += $vals['points'];
$res[$vals['id']]['id'] = $vals['id'];
}
else{
$res[$vals['id']] = $vals;
}
}
$result = array();
foreach ($res as $item){
$result[] = (object) $item;
}
output enter image description here
Parse JSON as Object
Aggregate Data
Put back as JSON
$json = <<<'_JSON'
[
{
"id": "xx",
"points": 25
},
{
"id": "xx",
"points": 40
},
{
"id": "xy",
"points": 40
}
]
_JSON;
$aggregate = [];
foreach(json_decode($json) as $data) {
if(!isset($aggregate[$data->id])) $aggregate[$data->id] = 0;
$aggregate[$data->id] += $data->points;
}
$output = [];
foreach($aggregate as $id => $points) {
$output[] = ['id' => $id, 'points' => $points];
}
echo json_encode($output);
[{"id":"xx","points":65},{"id":"xy","points":40}]
You may use array_reduce buil-in function to do the job. Also, when looping through the object's array (the callback), you should check if the result array has the current item's ID to verify that wether you need to add the item to the result array or to make the sum of points attributes.
Here's an example:
// a dummy class just to replicate the objects with ID and points attributes
class Dummy
{
public $id;
public $points;
public function __construct($id, $points)
{
$this->id = $id;
$this->points = $points;
}
}
// the array of objects
$arr = [new Dummy('xx', 25), new Dummy('xx', 40), new Dummy('xy', 40)];
// loop through the array
$res = array_reduce($arr, function($carry, $item) {
// holds the index of the object that has the same ID on the resulting array, if it stays NULL means it should add $item to the result array, otherwise calculate the sum of points attributes
$idx = null;
// trying to find the object that has the same id as the current item
foreach($carry as $k => $v)
if($v->id == $item->id) {
$idx = $k;
break;
}
// if nothing found, add $item to the result array, otherwise sum the points attributes
$idx === null ? $carry[] = $item:$carry[$idx]->points += $item->points;
// return the result array for the next iteration
return $carry;
}, []);
This will result in something like this:
array(2) {
[0]=>
object(Dummy)#1 (2) {
["id"]=>
string(2) "xx"
["points"]=>
int(65)
}
[1]=>
object(Dummy)#3 (2) {
["id"]=>
string(2) "xy"
["points"]=>
int(40)
}
}
Hope that helps, feel free to ask for further help.
Let's use a helper variable called $map:
$map = [];
Build your map:
foreach ($input => $item) {
if (!isset($map[$item["id"]])) $map[$item["id"]] = 0;
$map[$item["id"]] += $item["points"];
}
Now let's build the output:
$output = [];
foreach ($map as $key => $value) {
$output[] = (object)["id" => $key, "points" => $value];
}
[
{
"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,
)
*/
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);
I have 3 arrays for storing posts,comments, and likes.
These are the JSON strings:
//comments JSON (stores user and comment points)
$comments='[
{
"user": "5",
"points": "12"
},
{
"user": "2",
"points": "1"
},
{
"user": "3",
"points": "1"
}
]';
//likes(stores user and likes point)
$likes='[
{
"user": "1",
"points": 7
},
{
"user": "4",
"points": 4
},
{
"user": "3",
"points": 1
}
]';
//posts (stores user and post points)
$posts='[
{
"user": "1",
"points": "6"
},
{
"user": "3",
"points": "2"
},
{
"user": "2",
"points": "1"
}
]';
I convert these JSONs into arrays like this:
$comment_array = json_decode($comments,TRUE);
$like_array = json_decode($likes,TRUE);
$post_array = json_decode($posts,TRUE);
//echo '<pre>';
//print_r($comment_array);
//print_r($like_array);
//print_r($post_array);
//echo '</pre>';
Now, I'm trying to sum these points and save the result in a new array. It's not mandatory that a user should have entries in all the three arrays. It depends on whether a user has made a comment, post or like.
function mergeArrays($filenames, $titles, $descriptions) {
$result = array();
foreach ( $filenames as $key=>$name ) {
$result[] = array( 'filename' => $name, 'title' => $titles[$key], 'descriptions' => $descriptions[ $key ] );
}
return $result;
}
The above function can merge all the three arrays.
$merged= mergeArrays($comment_array, $like_array, $post_array);
echo '<pre>';
print_r($merged);
echo '</pre>';
However, each array after merging is stored as an index element.
How can I get a result something like this:
$result='[
{
"user": "1",
"points": "13"
},
{
"user": "2",
"points": "2"
},
{
"user": "3",
"points": "4"
},
{
"user": "4",
"points": "4"
},
{
"user": "5",
"points": "12"
}
]';
Considering your three arrays, this code will get you an array with: points, votes and diferent users.
Edit: Adding additional array and printing it to get the output desired by question.
$points = 0;
$uniqueUsers = array();
$votes = 0;
$users = 0;
$result = array();
//Comments
if (!empty($comment_array)) {
foreach ($comment_array as $item) {
if (!in_array($item['user'], $uniqueUsers)) {
array_push($uniqueUsers, $item['user']);
$result[$item['user']] = 0;
}
$votes ++;
$result[$item['user']] += $item['points'];
}
}
// Likes
if (!empty($like_array)) {
foreach ($like_array as $item) {
if (!in_array($item['user'], $uniqueUsers)) {
array_push($uniqueUsers, $item['user']);
$result[$item['user']] = 0;
}
$votes ++;
$result[$item['user']] += $item['points'];
}
}
// Posts
if (!empty($post_array)) {
foreach ($post_array as $item) {
if (!in_array($item['user'], $uniqueUsers)) {
array_push($uniqueUsers, $item['user']);
$result[$item['user']] = 0;
}
$votes ++;
$result[$item['user']] += $item['points'];
}
}
foreach ($result as $idUser=>$points) {
echo "\n";
echo "\n" . 'User: ' . $idUser;
echo "\n" . 'Points: ' . $points;
}
$results = array('users'=> count($uniqueUsers), 'votes'=>$votes, 'points'=> $points);
//print_r($results);
The solution using array_column, array_walk_recursive and array_values functions:
...
$comments = array_column($comment_array, 'points', 'user');
$likes = array_column($like_array, 'points', 'user');
$posts = array_column($post_array, 'points', 'user');
$list = [$comments, $likes, $posts];
$result = [];
array_walk_recursive($list, function($v, $k) use(&$result){
if (key_exists($k, $result)){
$result[$k]['points'] += $v;
} else {
$result[$k] = ['user' => $k, 'points' => $v];
}
});
$result = array_values($result);
print_r($result);
The output:
Array
(
[0] => Array
(
[user] => 5
[points] => 12
)
[1] => Array
(
[user] => 2
[points] => 2
)
[2] => Array
(
[user] => 3
[points] => 4
)
[3] => Array
(
[user] => 1
[points] => 13
)
[4] => Array
(
[user] => 4
[points] => 4
)
)
Do the following to get one array with summed points:
$collections = array(
'comments' => json_decode($comments,TRUE),
'likes' => json_decode($likes,TRUE);,
'posts' => json_decode($posts,TRUE),
);
$newArray = array();
foreach ($collections as $collection) {
foreach ($collection as $user) {
$newArray[$user->user] += $user->points;
}
}
There are two important points to make if you want to learn the "best" way to handle these types of operations.
Don't use iterated in_array() calls when isset() can be used instead. This is because isset() is much more efficient than in_array().
Use temporary keys to identify duplicate occurrences, then re-index your results when finished -- usually with array_values(), but this time I used array_multisort() to re-order the results AND re-index.
Code: (Demo)
$merged = array_merge(json_decode($comments, true), json_decode($likes, true), json_decode($posts, true));
$result = [];
foreach ($merged as $entry) {
if (!isset($result[$entry['user']])) {
$result[$entry['user']] = $entry;
} else {
$result[$entry['user']]['points'] += $entry['points'];
}
}
array_multisort(array_column($result, 'user'), $result);
// usort($result, function($a, $b) { return $a['user'] <=> $b['user']; });
// array_multisort() will outperform `usort()` in this case.
echo json_encode($result);
Output:
[{"user":"1","points":13},{"user":"2","points":2},{"user":"3","points":4},{"user":"4","points":4},{"user":"5","points":"12"}]
Decode each array and merge them together into a multi-dimensional array.
Iterate each subarray and determine if it is the first occurrence of the user. If so, retain the entire subarray. If not, only increase the points tally within that subarray.
When the loop is finished, sort by user ascending.
This is clean, direct, and readable.