Remove childless elements (except leafs) from nested array - javascript

I've got a table called mappings with id, leaf, parent_id, name and flip_parent columns. parent_id and flip_parent are both integers referring to the mapping.id column. flip_parent holds an id of a value that should be excluded from the tree. To do so I've got the following function ($mappings are all rows from the mappings table, flipParentIds are all values from the same table where the flip_parent value is not null)
private function removeFlipParents(array $mappings, array $flipParentIds)
{
foreach ($mappings as $key => $mapping) {
foreach ($flipParentIds as $id) {
if ($mapping['id'] === $id['flipParent']) {
unset($mappings[$key]);
}
}
}
return $mappings;
}
After those values have been removed, I need to build a tree with the remaining data (the tree goes 5/6 levels deep), which is done with the following piece of code;
private function buildTree(array $elements, $parentId)
{
$branch = [];
foreach ($elements as $element) {
if ($element['parentId'] == $parentId) {
$children = $this->buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
} else {
$element['children'] = [];
}
}
}
return $branch;
}
In this case elements is the same array as $mappings, but without those flip parents. The result of this function is returned as a JSON response and handled by Javascript to build a tree. The returned JSON has a structure similar to this;
[{
"id": 1, "name": "Node 1", "children": [{
"id": 2, "name": "Node 1.1", "children": [{
"id": 4, "name": "Node 1.1.1", "leaf": true, "children": [], "gls": [{
"id": 1000, "name": "GL1", "code": "0100"
}, {
"id": 1001, "name": "GL2", "code": "0200"
}]
}, {
"id": 5, "name": "Node 1.1.2", "leaf": true, "children": [], "gls": [{
"id": 2000, "name": "GL3", "code": "0300"
}, {
"id": 2001, "name": "GL4", "code": "0400"
}]
}]
}, {
"id": 3, "name": "Node 1.2", "children": [{
"id": 6, "name": "Node 1.2.1", "leaf": true, "children": [], "gls": [{
"id": 3000, "name": "GL5", "code": "0500"
}, {
"id": 3001, "name": "GL6", "code": "0600"
}]
}]
}]
},
{
"id": 7, "name": "Node 2", "children": [{
"id": 8, "name": "Node 2.1", "children": [{
"id": 9, "name": "Node 2.1.1", "leaf": true, "children": [], "gls": [{
"id": 4000, "name": "GL7", "code": "0700"
}, {
"id": 4001, "name": "GL8", "code": "0800"
}]
}]
}]
}
]
To build the tree in JS I've got the following two functions
parseNodes(nodes)
{
const ul = document.createElement('ul');
ul.style.listStyleType = 'none';
nodes.forEach((node) => {
const parsedNode = this.parseNode(node);
if (parsedNode) {
ul.appendChild(parsedNode);
}
});
return ul;
}
parseNode(node)
{
if (node.name && node.name !== '') {
const li = document.createElement('li');
li.className = node.leaf ? 'leaf' : 'parent';
li.setAttribute('data-mapping-id', node.id);
if (node.parentId) {
li.setAttribute('data-parent-id', node.parentId);
}
li.innerHTML = node.name;
if (node.children) {
li.appendChild(this.parseNodes(node.children));
}
return li;
}
return null;
}
This all works perfectly fine, but I now want to remove childless, non-leaf nodes from the tree, as only the leafs will actually be usable. I don't know at what level the childless nodes are, and not all childless nodes are at the same level.
To do so I've written a recursive PHP function, which is called after removeFlipParents and buildTree (this needs to be done as I need the tree structure to check whether branches have children of not);
private function removeChildlessBranches(array $nodes)
{
foreach ($nodes as $key => $node) {
if (empty($node['children']) && !$node['leaf']) {
unset($nodes[$key]);
} else {
$this->removeChildlessBranches($node['children']);
}
}
return $nodes;
}
This kinda does what I want it to do as it removes all childless root nodes that aren't leafs, but it doesn't go to the 2nd level and beyond. It also changes the structure of $nodes. Without the removeChildlessBranches call the JSON is wrapped in an array ([{"id": 1, "name": "Node 1", ...}]) and I can iterate over it in the parseNodes function, but if I do use the removeChildlessBranches call, $nodes no longer is wrapped in an array ({"id": 1, "name": "Node 1", ...}) and can't be iterated over.
So my two questions are, how can I make removeChildlessBranches actually recursive, and how can I make it so it doesn't change the structure of $nodes?

You're passing in a copy of the $node['children'] array on this line $this->removeChildlessBranches($node['children']); (If you haven't heard of it before lookup pass by reference and pass by value). So any subsequent changes are being made to that copy and not your original array (which is also a copy). The results of the changes are then being thrown away as you're not doing anything with them.
You could fix that issue by changing the line to this $nodes[$key]['children'] = $this->removeChildlessBranches($node['children']);.
Be aware however, that you may now have a node which has no children and is not a leaf but will not be removed correctly as you've already trimmed at that level. Trimming out children first and then unsetting should give you the desired result:
private function removeChildlessBranches(array $nodes)
{
foreach ($nodes as $key => $node) {
$nodes[$key]['children'] = $this->removeChildlessBranches($node['children']);
if (empty($nodes[$key]['children']) && !$nodes[$key]['leaf']) {
unset($nodes[$key]);
}
}
return $nodes;
}

Related

In a nested array of objects and using a value of key 1, get the value of key 2

I have a json file of folders.
folders.json
[
{
"name": "Documents",
"files": [
{
"name": "Quarterly Results",
"type": "Document"
}
]
},
{
"name": "Favourites",
"files": [
{
"name": "Brawl Stars",
"files": [
{
"name": "NS dying in 5 seconds",
"type": "Video"
},
{
"name": "Josiah raping NS",
"type": "Video"
}
]
},
{
"name": "Coding",
"files": [
{
"name": "Coding is so fun",
"type": "Image"
},
{
"name": "I love svelte",
"files": [
{
"name": "REPLs",
"files": [
{
"name": "REPL 1",
"type": "Image"
},
{
"name": "REPL 2",
"type": "Videp"
}
]
},
{
"name": "oh nooo",
"type": "Document"
}
]
}
]
},
{
"name": "Favourites 1",
"type": "Document"
},
{
"name": "Favourites 2",
"type": "Video"
},
{
"name": "Favourites 3",
"type": "Image"
}
]
},
{
"name": "Knowledge Base 1",
"type": "Video"
}
]
Let's say I have the folder "name" in folders.json. I want to find all file names inside it.
For example, given a folder "Favourites", I want to output:
[{name: 'Brawl Stars', files: Array(2)}, {name: 'Coding', files: Array(2)} {"name": "Favourites 1", "type": "Document"}, {"name": "Favourites 2","type": "Video"},{"name": "Favourites 3", "type": "Image"}]
Note: A folder is when there are files inside the same dictionary as name.
Thus, using a value of key: name, output out the values of files in this nested array of objects.
I have tried the following: root.filter(item => item.name === currentFolder).map(item => item.files), where root is the array of objects, currentFolder is the folder "name" in folder.json.
However, this only works for depth of 2, where the folder is not nested in other folders.
I have tried looking for other answers, but all other answers only work for until depth of 2, thus needing help.
I am currently using sveltekit framework, not using jquery.
Try some recursion. Loop over every folder, and return the folder if it is what we want. Otherwise, do the same process for that folder. Just like this:
function getFolder(folders, name) {
for (const folder of folders) {
// skip if this isn't a folder
if (!folder.files) continue;
// if this folder is what we're looking for, then return
if (folder.name === name) return folder;
// else recurse and return if we get a result
const subsearch = getFolder(folder.files, name);
if (subsearch) return subsearch;
}
// if there's no matches, return null
return null
}
const folders = [{name:"Documents",files:[{name:"Quarterly Results",type:"Document"}]},{name:"Favourites",files:[{name:"Brawl Stars",files:[{name:"NS dying in 5 seconds",type:"Video"},{name:"Josiah raping NS",type:"Video"}]},{name:"Coding",files:[{name:"Coding is so fun",type:"Image"},{name:"I love svelte",files:[{name:"REPLs",files:[{name:"REPL 1",type:"Image"},{name:"REPL 2",type:"Videp"}]},{name:"oh nooo",type:"Document"}]}]},{name:"Favourites 1",type:"Document"},{name:"Favourites 2",type:"Video"},{name:"Favourites 3",type:"Image"}]},{name:"Knowledge Base 1",type:"Video"}];
console.log(getFolder(folders, 'Favourites'))
You need to use recursion when you search multiple layers of nested data.
I found a solution here
function findNested(obj, key, value) {
// Base case
if (obj[key] === value) {
return obj;
} else {
var keys = Object.keys(obj); // add this line to iterate over the keys
for (var i = 0, len = keys.length; i < len; i++) {
var k = keys[i]; // use this key for iteration, instead of index "i"
// add "obj[k] &&" to ignore null values
if (obj[k] && typeof obj[k] == "object") {
var found = findNested(obj[k], key, value);
if (found) {
// If the object was found in the recursive call, bubble it up.
return found;
}
}
}
}
}
then you can just do this: (where 'folders' is your data)
findNested(folders, "name", "REPLs").files

Filtering and storing and finding parent child relationship in elements of array [duplicate]

This question already has answers here:
How to find a node in a tree with JavaScript
(19 answers)
Javascript - Find path to object reference in nested object
(3 answers)
Closed 1 year ago.
I am working on an angular application. My data is as follows
data= [
{
"id": 2,
"name": "Jamie",
"objectId": 200,
"parentId": null,
"children": [
{
"id": 98,
"name": "Rose",
"objectId": 100,
"parentId": 200,
"children": [
{
"id": 1212,
"name": "julie",
"objectId": 500,
"parentId": 100,
"children": []
}
]
},
{
"id": 67,
"name": "Kosy",
"objectId": 700,
"parentId": 200,
"children": []
}
]
}
]
I will be having input id and name. Suppose in my method I get id as 1212 and name as "julie". So I want to go to that node whose id is 1212 and name is equal to
"julie", once this condition is met. I need to check parentId in children is equal to objectId in parent till parentId becomes null.
If parent id becomes null then it is considered as last node and then I want to have my data in the array in following format. For id 1212 and name
"julie" resultArray is
resultArray = ["Jamie/Rose/Julie "]. Data starting from parent to children separated by slash.
Another example is if I get id as 67 and name "Kosy". then result array will be
resultArray = ["Jamie/Kosy"]
As parentId of Kosy is 200 and ObjectId of Jamie is 200, which indicate that Jamie is parent of Kosy, that's why data should look like this. want to have dynamic code as at run time data can be huge but structure and logic will be same as mentioned above
How can I do this
I wrote the code for this problem. Please try the following code. This is one of the typical tree-search problem. One point what sets it apart from traditional tree search is checking its parent. That was easily solved here.
const data = [
{
"id": 2,
"name": "Jamie",
"objectId": 200,
"parentId": null,
"children": [
{
"id": 98,
"name": "Rose",
"objectId": 100,
"parentId": 200,
"children": [
{
"id": 1212,
"name": "julie",
"objectId": 500,
"parentId": 100,
"children": []
}
]
},
{
"id": 67,
"name": "Kosy",
"objectId": 700,
"parentId": 200,
"children": []
}
]
}
];
// function that search the target in a node(not a array)
// In order for this function to return true, id comparison from the final node to this node should be passed as well
const findInNode = (node, id, name, output) => {
if (node.name === name && node.id === id) { // self test
return true;
} else {
const children = node.children;
if (!children) return false;
// find in children
for (let child of children) {
// output.paths is the current search path
output.paths.push(child.name);
if (findInNode(child, id, name, output)) {
// if found, compare the self objectId and child's parentId
return child.parentId === node.objectId;
}
output.paths.pop();
}
}
}
// function that search the target in an array, for use at the top-most level
const findInArray = (nodes, id, name, output) => {
for (let node of nodes) {
output.paths.push(node.name);
if (findInNode(node, id, name, output)) {
if (!node.parentId) {
output.found = true;
break;
}
}
output.paths.pop();
}
}
output = { paths: [], found: false };
findInArray(data, 1212, 'julie', output);
console.log(output.found);
console.log(output.paths.join('/'));

Trying to find element recursively in tree javascript

I'm trying to figure out how to search recursively element in json tree. For example I'm trying right now to reach 'Fruits' but can't figure out how to do it. Here's my json object
[
{
"_id": "604cab0acbdb8c1060698419",
"name": "Grocery",
"children": [
{
"children": [
{
"name": "Fruits",
"price": "200"
}
],
"_id": "604cad9b4ae51310c6f313f6",
"name": "Potatoes",
"price": "200"
},
{
"children": [],
"_id": "604cae721257d510e679a467",
"name": "Vegetables"
}
],
"date": "2021-03-13T12:07:38.186Z",
"__v": 0
} ]
function findName(name, tree) {
if(tree.children.name == name {
return tree;
}
if(tree.child == 0) {
return
}
return findName(name, tree);
};
There are a couple of issues with your implementation.
Your starting point is an array, but you treat it as though it were a node by trying to use its children property.
You're looking for name on children, but children is an array.
You're passing the same thing into findName at the end that you received. If you reach that point, you'll constantly call yourself until you run out of stack space.
Loop through the nodes in the array checking them and their children; see comments:
function findName(name, children) {
if (Array.isArray(children)) {
// Yes, check them
for (const childNode of children) {
if (childNode.name === name) {
// Found it
return childNode;
}
// Look in this node's children
const found = findName(name, childNode.children);
if (found) {
// Found in this node's children
return found;
}
}
}
}
Live Example:
const tree = [
{
"_id": "604cab0acbdb8c1060698419",
"name": "Grocery",
"children": [
{
"children": [
{
"name": "Fruits",
"price": "200"
}
],
"_id": "604cad9b4ae51310c6f313f6",
"name": "Potatoes",
"price": "200"
},
{
"children": [],
"_id": "604cae721257d510e679a467",
"name": "Vegetables"
}
],
"date": "2021-03-13T12:07:38.186Z",
"__v": 0
} ];
function findName(name, children) {
if (Array.isArray(children)) {
// Yes, check them
for (const childNode of children) {
if (childNode.name === name) {
// Found it
return childNode;
}
// Look in this node's children
const found = findName(name, childNode.children);
if (found) {
// Found in this node's children
return found;
}
}
}
}
console.log(findName("Fruits", tree));
const object = [
{
"_id": "604cab0acbdb8c1060698419",
"name": "Grocery",
"children": [
{
"children": [
{
"name": "Fruits",
"price": "200"
}
],
"_id": "604cad9b4ae51310c6f313f6",
"name": "Potatoes",
"price": "200"
},
{
"children": [],
"_id": "604cae721257d510e679a467",
"name": "Vegetables"
}
],
"date": "2021-03-13T12:07:38.186Z",
"__v": 0
} ]
function find(name, tree) {
// tree is undefined, return `undefined` (base case)
if (!tree) return
if (Array.isArray(tree)) {
// tree is an array
// so iterate over every object in the array
for (let i = 0; i < tree.length; i++) {
const obj = tree[i]
const result = find(name, obj)
// `find` returned non-undefined value
// means match is found, return it
if (result) return result
// no match found in `obj` so continue
}
} else if (tree.name === name) {
// `tree` is a key-value object
// and has matching `name`
return tree
}
// if no tree matching `name` found on the current `tree`
// try finding on its `children`
return find(name, tree.children)
}
console.log(find("Fruits", object))

Reverse Traverse a hierarchy

I have a hierarchy of objects that contain the parent ID on them. I am adding the parentId to the child object as I parse the json object like this.
public static fromJson(json: any): Ancestry | Ancestry[] {
if (Array.isArray(json)) {
return json.map(Ancestry.fromJson) as Ancestry[];
}
const result = new Ancestry();
const { parents } = json;
parents.forEach(parent => {
parent.parentId = json.id;
});
json.parents = Parent.fromJson(parents);
Object.assign(result, json);
return result;
}
Any thoughts on how to pull out the ancestors if I have a grandchild.id?
The data is on mockaroo curl (Ancestries.json)
As an example, with the following json and a grandchild.id = 5, I would create and array with the follow IDs
['5', '0723', '133', '1']
[{
"id": "1",
"name": "Deer, spotted",
"parents": [
{
"id": "133",
"name": "Jaime Coldrick",
"children": [
{
"id": "0723",
"name": "Ardys Kurten",
"grandchildren": [
{
"id": "384",
"name": "Madelle Bauman"
},
{
"id": "0576",
"name": "Pincas Maas"
},
{
"id": "5",
"name": "Corrie Beacock"
}
]
},
There is perhaps very many ways to solve this, but in my opinion the easiest way is to simply do a search in the data structure and store the IDs in inverse order of when you find them. This way the output is what you are after.
You could also just reverse the ordering of a different approach.
I would like to note that the json-structure is a bit weird. I would have expected it to simply have nested children arrays, and not have them renamed parent, children, and grandchildren.
let data = [{
"id": "1",
"name": "Deer, spotted",
"parents": [
{
"id": "133",
"name": "Jaime Coldrick",
"children": [
{
"id": "0723",
"name": "Ardys Kurten",
"grandchildren": [
{
"id": "384",
"name": "Madelle Bauman"
},
{
"id": "0576",
"name": "Pincas Maas"
},
{
"id": "5",
"name": "Corrie Beacock"
}
]
}]
}]
}]
const expectedResults = ['5', '0723', '133', '1']
function traverseInverseResults(inputId, childArray) {
if(!childArray){ return }
for (const parent of childArray) {
if(parent.id === inputId){
return [parent.id]
} else {
let res = traverseInverseResults(inputId, parent.parents || parent.children || parent.grandchildren) // This part is a bit hacky, simply to accommodate the strange JSON structure.
if(res) {
res.push(parent.id)
return res
}
}
}
return
}
let result = traverseInverseResults('5', data)
console.log('results', result)
console.log('Got expected results?', expectedResults.length === result.length && expectedResults.every(function(value, index) { return value === result[index]}))

How to delete object from an array of objects having relations with each arrays?

This Object have relationship as: childOne > childTwo > childThree > childFour > childFive > childSix.
{
"parentObj": {
"childOne": [
{
"name": "A",
"id": "1"
},
{
"name": "B",
"id": "2"
}
],
"childTwo": [
{
"name": "AB",
"parent_id": "1",
"id": "11"
},
{
"name": "DE",
"parent_id": "2",
"id": "22"
}
],
"childThree": [
{
"name": "ABC",
"parent_id": "22",
"id": "111"
},
{
"name": "DEF",
"parent_id": "11",
"id": "222"
}
],
"childFour": [
{
"name": "ABCD",
"parent_id": "111",
"id": "1111"
},
{
"name": "PQRS",
"parent_id": "111",
"id": "2222"
}
],
"childFive": [
{
"name": "FGRGF",
"parent_id": "1111",
"id": "11111"
},
{
"name": "ASLNJ",
"parent_id": "1111",
"id": "22222"
},
{
"name": "ASKJA",
"parent_id": "1111",
"id": "33333"
}
],
"childSix": [
{
"name": "SDKJBS",
"parent_id": "11111",
"id": "111111"
},
{
"name": "ASKLJB",
"parent_id": "11111",
"id": "222222"
}
]
}
}
Is there any way to delete an item by ID and the objects which are associated with that particular ID should get deleted(i.e., If I do delete parentObj.childTwo[1], then all the related object beneath it should also gets deleted).
Looping manually is too bad code, and generate bugs. There must be better ways of dealing with this kind of problems like recursion, or other.
The data structure does not allow for efficient manipulation:
By nature objects have an non-ordered set of properties, so there is no guarantee that iterating the properties of parentObj will give you the order childOne, childTwo, childThree, ... In practice this order is determined by the order in which these properties were created, but there is no documented guarantee for that. So one might find children before parents and vice versa.
Although the id values within one such child array are supposed to be unique, this object structure does not guarantee that. Moreover, given a certain id value, it is not possible to find the corresponding object in constant time.
Given this structure, it seems best to first add a hash to solve the above mentioned disadvantages. An object for knowing a node's group (by id) and an object to know which is the next level's group name, can help out for that.
The above two tasks can be executed in O(n) time, where n is the number of nodes.
Here is the ES5-compatible code (since you mentioned in comments not to have ES6 support). It provides one example call where node with id "1111" is removed from your example data, and prints the resulting object.
function removeSubTree(data, id) {
var groupOf = {}, groupAfter = {}, group, parents, keep = { false: [], true: [] };
// Provide link to group per node ID
for (group in data) {
data[group].forEach(function (node) {
groupOf[node.id] = group;
});
}
// Create ordered sequence of groups, since object properties are not ordered
for (group in data) {
if (!data[group].length || !data[group][0].parent_id) continue;
groupAfter[groupOf[data[group][0].parent_id]] = group;
}
// Check if given id exists:
group = groupOf[id];
if (!group) return; // Nothing to do
// Maintain list of nodes to keep and not to keep within the group
data[group].forEach(function (node) {
keep[node.id !== id].push(node);
});
while (keep.false.length) { // While there is something to delete
data[group] = keep.true; // Delete the nodes from the group
if (!keep.true.length) delete data[group]; // Delete the group if empty
// Collect the ids of the removed nodes
parents = {};
keep.false.forEach(function (node) {
parents[node.id] = true;
});
group = groupAfter[group]; // Go to next group
if (!group) break; // No more groups
// Determine what to keep/remove in that group
keep = { false: [], true: [] };
data[group].forEach(function (node) {
keep[!parents[node.parent_id]].push(node);
});
}
}
var tree = {"parentObj": {"childOne": [{"name": "A","id": "1"},{"name": "B","id": "2"}],"childTwo": [{"name": "AB","parent_id": "1","id": "11"},{"name": "DE","parent_id": "2","id": "22"}],"childThree": [{"name": "ABC","parent_id": "22","id": "111"},{"name": "DEF","parent_id": "11","id": "222"}],"childFour": [{"name": "ABCD","parent_id": "111","id": "1111"},{"name": "PQRS","parent_id": "111","id": "2222"}],"childFive": [{"name": "FGRGF","parent_id": "1111","id": "11111"},{"name": "ASLNJ","parent_id": "1111","id": "22222"},{"name": "ASKJA","parent_id": "1111","id": "33333"}],"childSix": [{"name": "SDKJBS","parent_id": "11111","id": "111111"},{"name": "ASKLJB","parent_id": "11111","id": "222222"}]}}
removeSubTree(tree.parentObj, "1111");
console.log(tree.parentObj);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Sure, the function you use to delete an entry should FIRST recurse, which means run itself on the linked entry, unless there is none. So, in psuedocode
function del(name, index)
{
if parent[name][index] has reference
Then del(reference name, reference ID)
Now del parent[name][index]
}
No loop needed.
And since we stop if there is no reference, we do not recurse forever.
Not sure what it is you want but maybe this will work:
const someObject = {
"parentObj": {
"childOne": [
{
"name": "A",
"id": "1"
},
{
"name": "B",
"id": "2"
}
],
"childTwo": [
{
"name": "AB",
"childOne": "1",
"id": "11"
},
{
"name": "DE",
"childOne": "2",
"id": "22"
}
]
}
};
const removeByID = (key,id,parent) =>
Object.keys(parent).reduce(
(o,k)=>{
o[k]=parent[k].filter(
item=>
!(Object.keys(item).includes(key)&&item[key]===id)
);
return o;
},
{}
);
const withoutID = Object.assign(
{},
someObject,
{ parentObj : removeByID("childOne","1",someObject.parentObj) }
);
console.log(`notice that childTwo item with childOne:"1" is gone`);
console.log("without key:",JSON.stringify(withoutID,undefined,2));
const otherExample = Object.assign(
{},
someObject,
{ parentObj : removeByID("childOne","2",someObject.parentObj) }
);
console.log(`notice that childTwo item with childOne:"2" is gone`);
console.log("without key:",JSON.stringify(otherExample,undefined,2));
const both = Object.assign(
{},
someObject,
{ parentObj : removeByID("childOne","1",otherExample.parentObj) }
);
console.log(`notice that childTwo items with childOne are both gone`);
console.log("without key:",JSON.stringify(both,undefined,2));

Categories

Resources