I have flat array like:
[
{
"id": "1",
"parentId": "0",
"cost": 1000
},
{
"id": "2",
"parentId": "1",
"cost": 2000
},
{
"id": "3",
"parentId": "2",
"cost": 4000
},
...
]
Requirement:
convert flat array to tree array --> (DONE)
sum of each id is the total price of it and its child
now the problem appears:
should summation be done before or after converting from flat array to tree array
This is my code is try convert flat to tree:
public function buildTree(array $flat)
{
$grouped = [];
$fnBuilder = function ($companies) use (&$fnBuilder, $grouped) {
foreach ($companies as $k => $company) {
$id = $company['id'];
if (isset($grouped[$id])) {
$company['children'] = $fnBuilder($grouped[$id]);
}
$companies[$k] = $company;
}
return $companies;
};
return $fnBuilder($grouped[0]);
}
My expect result is like:
[
{
"id": "1",
"sum": 7000,
"children": [
{
"id": "2",
"sum": 6000,
"children": [
{
"id": "3",
"sum": 4000,
},
I wonder if it's possible to handle the summing inside the buildTree?
My idea is to have a tree and then handle the sum of sublevels, but i can't handle assigning the sum to the parent element
I created a class and incorporated your ideas.
class TreeBuilder {
private $flatArr;
private $idToNode;
public function __construct($flatArr) {
// Keep the flat arr in case we need it.
$this->flatArr = $flatArr;
// Create an array to lookup a node to determine if it exists.
$this->idToNode = array_combine(array_column($flatArr, 'id'), $flatArr);
}
public function buildTree() {
// create an empty array to hold root nodes
$roots = [];
// iterate through each node and add it to its parent's children list
foreach ($this->flatArr as &$node) {
$id = $node['id'];
$parentId = $node['parentId'];
if (isset($this->idToNode[$parentId])) {
$this->out("add child to $parentId " . print_r($node, true));
$parentNode = &$this->idToNode[$parentId];
if ( isset($parentNode['children']) ) {
$parentNode['children'] = [&$this->idToNode[$id]];
} else {
$parentNode['children'][] = &$this->idToNode[$id];
}
// $children[] = &$node;
} else {
$this->out("add to root " . print_r($node, true));
$roots[] = &$this->idToNode[$id];
}
}
// calculate the sum of each node and its children recursively
foreach ($roots as &$root) {
$this->calculateSum($root);
}
return $roots;
}
private function calculateSum(&$node) {
// calculate the sum of the current node
$node['sum'] = $node['cost'];
// recursively calculate the sum of the children nodes
$children = &$node['children'];
if (isset($children)) {
foreach ($children as &$child) {
$node['sum'] += $this->calculateSum($child);
}
}
return $node['sum'];
}
private function out($s) {
echo "$s\n";
}
}
You could build the tree without recursion, and then use recursion to update the sum, in post-order depth first order:
function buildTree(array $flat) {
foreach ($flat as ["id" => $id, "cost" => $sum]) {
$keyed[$id] = ["id" => $id, "sum" => $sum];
}
foreach ($flat as ["id" => $id, "parentId" => $parentId]) {
if (isset($keyed[$parentId])) {
$keyed[$parentId]["children"][] = &$keyed[$id];
} else {
$root = &$keyed[$id];
}
}
function updateSum(&$node) {
foreach ($node["children"] ?? [] as &$child) {
$node["sum"] += updateSum($child);
}
return $node["sum"];
}
updateSum($root);
return $root;
}
Example run:
$flat = json_decode('[
{
"id": "1",
"parentId": "0",
"cost": 1000
},
{
"id": "2",
"parentId": "1",
"cost": 2000
},
{
"id": "3",
"parentId": "2",
"cost": 4000
}
]', true);
$root = buildTree($flat);
print_r($root);
Related
Controller: for get data from session
public function SessionDestroy(Request $request)
{
if ($request->session()->has('data')) {
return $request->session()->get('data');
} else {
return "No Data";
}
}
this is session data i have and i want remove a single array:
[
{
"app_date": "2022-03-16",
"department": "2",
"doctor": "4",
"fee": "150"
},
{
"app_date": "2022-03-17",
"department": "2",
"doctor": "4",
"fee": "150"
},
{
"app_date": "2022-03-16",
"department": "2",
"doctor": "4",
"fee": "150"
}
]
So, How can i remove a single Array or Item
you don't have a unique id in your session to remove by this unique id ..
this may help you !
public function SessionDestroy(Request $request)
{
if (session()->has('data')) {
session()->forget('data');
if(!empty(request('app_date')) && is_array(request('app_date')))
$new_data = [];
$x = 0;
foreach(request('app_date') as $app_date){
$new_data[]['app_date'] = $app_date;
$new_data[]['department'] = request('department')[$x]??'';
$new_data[]['doctor'] = request('doctor')[$x]??'';
$new_data[]['fee'] = request('fee')[$x]??'';
}
session()->put('data', $new_data);
return session('data');
} else {
return "No Data";
}
}
I have some struggles how to map infinite object in Laravel.
So I have one table Categories that I'm getting in controller like:
$find_parent = Category::where('slug', $slug)->with('childrenRecursive')->first();
childrenRecursive() works fine in that case.
And return of this object would be like (minified):
{
"id": "1cbd459a-ccc0-435b-b9a9-0433e2e9285b",
"parent_id": "f0d29100-d2bc-48c8-89cf-985e0c03b8ac",
"children_recursive": [
{
"id": "30bf23a7-b28c-4s78-b873-1df589eebcb1",
"parent_id": "1cbd459a-ccc0-435b-b9a9-0433e2e9285b",
"children_recursive": [
{
"id": "32312a7-b28c-4s78-b873-1df589eebcb1",
"parent_id": "30bf23a7-b28c-4s78-b873-1df589eebcb1",
}
]
},
{
"id": "32bf23a7-b28c-4s78-b873-1df589eebcb1",
"parent_id": "1cbd459a-ccc0-435b-b9a9-0433e2e9285b",
"children_recursive": []
}
]
}
So from this, I have to get all id's in every object. So I have foreach function in controller that looks like:
$find_parent = Category::where('slug', $slug)->with('childrenRecursive')->first();
$array = [];
foreach($find_parent->childrenRecursive as $l){
array_push($array, $l->id);
if($l->childrenRecursive){
$result = array_merge($array, $this->catTree($l->childrenRecursive));
}
}
return $result;
And catTree would look like:
public function catTree($list){
$tree = [];
foreach($list as $l){
array_push($tree, $l->id);
if($l->childrenRecursive){
array_merge($tree, $this->catTree($l->childrenRecursive));
}
}
return $tree;
}
So this is returning some of the objects, but not everything. What am I doing wrong here?
I wan to divide fetched a single column from database in three different columns.
The below code gives me one column image_url.
public function fetch()
{
$model = Image::select('image_url');
return Datatables::eloquent($model)->make(true);
}
I want to split this column into three columns before returning it.
as for now it is returned in this form..
{
"draw": 0,
"recordsTotal": 16,
"recordsFiltered": 16,
"data": [
{
"image_url": "public/uploaded_images/2019_08_27_WheatMaizeBanner_1.png"
},
{
"image_url": "public/uploaded_images/2019_08_27_WheatFlourBanner1.jpg"
},
{
"image_url": "public/uploaded_images/2019_08_27_TurDalBanner1.jpg"
},
{
"image_url": "public/uploaded_images/2019_08_27_TurBanner1.png"
}
]
}
But I want something like this
{
"draw": 0,
"recordsTotal": 16,
"recordsFiltered": 16,
"data": [
{
"image1": "public/uploaded_images/2019_08_27_WheatMaizeBanner_1.png",
"image2": "public/uploaded_images/2019_08_27_WheatFlourBanner1.jpg"
"image3": "public/uploaded_images/2019_08_27_TurBanner1.png"
}
]
}
try this:
public function fetch()
{
$model = Image::select('image_url');
$result = Datatables::eloquent($model)->make(true);
$data = [];
foreach($result->data as $i => $record) {
$data['image' . ($i+1)] = $record->image_url;
}
$result->data = [ (object)$data ];
return $result
}
I would like to flatten an object. This is what I've got so far:
{
"1": {
"id": 1,
"name": "parent",
"children": {
"4": {
"id": 4,
"name": "child1",
"parent": 1
},
"5": {
"id": 5,
"name": "child2",
"parent": 1
}
}
},
"2":{
"id": 2,
"name": "parent2"
}
}
And this is what I would like to accomplish. So keep the same order but flatten the object:
{
"1": {
"id": 1,
"name": "parent",
},
"4": {
"id": 4,
"name": "child1",
"parent": 1
},
"5": {
"id": 5,
"name": "child2",
"parent": 1
},
"2": {
"id": 2,
"name": "parent2"
}
}
So far I haven't found a solution to this. I've tried a function without much success:
protected function _flattenObject($array)
{
static $flattened = [];
if(is_object($array) && count($array) > 0)
{
foreach ($array as $key => $member) {
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
$this->_flattenObject($member);
}
}
}
return $flattened;
}
The tough part for me is to keep the same order (children below its parent). And the function mentioned above also removes all objects and almost only keeps the keys with its value, so it wasn't a great success at all.
Hopefully somebody over here knows a good solution for this.
By the way, the reason I want such flatten structure is because the system I have to work with, has trouble handling multidimensional arrays and objects. And I still want to display an hierarchy, which is possible with the flatten structure I described, because the objects actually contain a "level" key as well so I can give them some padding based on the "level" while still showing up below their parent.
EDIT:
The JSON didn't seem to be valid, so I modified it a bit.
The main problem seems to be that you are not doing anything with the returned results of your recursive function. Unless using static inside a method does some magic that I don't know of...
So this section:
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
// What happens with the returned value?
$this->_flattenObject($member);
}
Should probably be more like this:
if(!is_object($member))
{
$flattened[$key] = $member;
} else
{
// Add the returned array to the array you already have
$flattened += $this->_flattenObject($member);
}
Here is code that works. It adds a field "level" to your objects, to represent how many levels deep in the original hierarchy they were.
<?php
$obj = json_decode('[{
"id": 1,
"name": "parent",
"children": [{
"id": 4,
"name": "child1",
"parent": 1
}, {
"id": 5,
"name": "child2",
"parent": 1
}]
}, {
"id": 2,
"name": "parent2"
}]');
function _flattenRecursive($array, &$flattened, &$level)
{
foreach ($array as $key => $member) {
$insert = $member;
$children = null;
if (is_array($insert->children)) {
$children = $insert->children;
$insert->children = array();
}
$insert->level = $level;
$flattened[] = $insert;
if ($children !== null) {
$level++;
_flattenRecursive($children, $flattened, $level);
$level--;
}
}
}
function flattenObject($array)
{
$flattened = [];
$level = 0;
_flattenRecursive($array, $flattened, $level);
return $flattened;
}
$flat = flattenObject($obj);
var_dump($flat);
?>
I want to create a JSON tree from a flat structure - in this case an App.net Thread.
I want JSON like this
"id": "12345",
"name": "Ringo",
"data":
{
"avatar": "",
"text": "We All Live",
},
"children": [{
"id": "34567",
"name": "John",
"data":
{
"avatar": "",
"text": "In a pink submarine?",
},
"children": [{
"id": "35555",
"name": "George",
"data":
{
"avatar": "",
"text": "Don't be daft",
},
"children": []
}]
},{
"id": "98765",
"name": "Paul",
"data":
{
"avatar": "",
"text": "In a yellow submarine?",
},
"children": []
}]
So, each post can have multiple children. Each child can have children.
The JSON coming back from App.net is not threaded.
{
"id": "98765",
"parent": "12345"
"details": {
...}
},
{
"id": "34567",
"parent": "12345"
"details": {
...}
},
I've used json_decode() to get the JSON response in to an array. I can iterate through using foreach.
How do I put each post in the correct part of the multi dimensional array?
Parent
|_
|-child
|-child
| |-child
|-child
etc
I would use references, the oft forgotten hard link of PHP. Something like this:
I'm assuming you have a $posts array that you've gotten back from an App.net API call.
(untested, may not compile / run / may have errors / may be more efficient ways to do this)
// first throw everything into an associative array for easy access
$references = array();
foreach ($posts as $post) {
$id = $post['id'];
$post['children'] = array();
$references[$id] = $post;
}
// now create the tree
$tree = array();
foreach ($references as &$post) {
$id = $post['id'];
$parentId = $post['parent'];
// if it's a top level object, add it to the tree
if (!$parentId) {
$tree[] =& $references[$id];
}
// else add it to the parent
else {
$references[$parentId]['children'][] =& $post;
}
// avoid bad things by clearing the reference
unset($post);
}
// encode it
print json_encode($tree);
Just sketching out an answer here: all assuming you can hold all entries in RAM otherwise you will have to make some ordering assumptions and clear our your array when a full unit is done.
Create an array posts indexed by id holding structures with the details and an array of children.
Then iterate over your input array and for each element:
create posts[id] if it hasn't been created yet
fill in details for post[id]
if there is a parent, lookup (creating it if needed -- no idea how the ordering is) posts[parent_id] and add this structure to the children there.
At the end you can iterate over all posts and those without a parent are the roots with their children properly filled in.
I wrote a class and example script to accomplish what I think you wanted.
It converts a flat structure into a hierarchical one, and also accounts for orphan updates (ones without parent updates available).
Update.php
<?php
/**
* An App.net update.
*/
class Update extends ArrayObject
{
/**
* The update's children.
*
* #var array
*/
private $children = array();
/**
* The parent update.
*
* #var Update
*/
private $parent;
/**
* Adds a child to this update.
*
* #param Update The child update.
*/
public function addChild(self $child)
{
$child->setParent($this);
$this->children[] = $child;
}
/**
* Sets the parent update.
*
* #param Update The parent update.
*/
public function setParent(self $parent = null)
{
$this->parent = $parent;
}
/**
* Converts the update and its children to JSON.
*
* #param boolean $encode Automatically encode?
*
* #return string The JSON-encoded update.
*/
public function toJson($encode = true)
{
$data = $this->getArrayCopy();
if ($this->children) {
$data['children'] = array();
foreach ($this->children as $child) {
$data['children'][] = $child->toJSON(false);
}
}
if ($encode) {
return json_encode($data);
}
return $data;
}
}
build.php
<?php
require 'Update.php';
$updates = <<<UPDATES
[
{
"id": "12345",
"name": "Ringo",
"data": {
"avatar": "",
"text": "We All Live"
}
},
{
"id": "34567",
"parent": "12345",
"name": "John",
"data": {
"avatar": "",
"text": "In a pink submarine?"
}
},
{
"id": "98765",
"parent": "12345",
"name": "Paul",
"data": {
"avatar": "",
"text": "In a yellow submarine?"
}
}
]
UPDATES;
$original = json_decode($updates, true);
$parents = array();
$children = array();
foreach ($original as $update) {
if (empty($update['parent'])) {
$parents[$update['id']] = $parent = new Update($update);
if (isset($children[$update['id']])) {
foreach ($children[$update['id']] as $child) {
$parent->addChild($child);
}
unset($children[$update['id']]);
}
} else {
$child = new Update($update);
if (isset($parents[$update['parent']])) {
$parents[$update['parent']]->addChild($child);
} else {
if (false === isset($children[$update['parent']])) {
$children[$update['parent']] = array();
}
$children[$update['parent']][] = $child;
}
}
}
// Anything in children at this point are orphans
echo "Parents:\n";
foreach ($parents as $parent) {
echo $parent->toJson();
}
echo "\n\nOrphans:\n";
foreach ($children as $parent => $orphans) {
foreach ($orphans as $orphan) {
echo $orphan->toJson();
}
}