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

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('/'));

Related

How to filter an objects where is employeeId equals to some value

I'm using knockoutjs, but the question is really in Javascript domain.
I have variable vm.filteredSerivces() which contains all services by all employees.
Now, I want to just preserve those filteredSerivces where is vm.filteredSerivces()[0].GroupedServices[x].EmployeeId == 3684 (x is the number of index number of each object in GroupedServices object list)
I tried as follows:
var filteredSrvcs = vm.filteredSerivces()[0].GroupedServices.filter(x => x.EmployeeId != Id).remove();
vm.filteredSerivces(filteredSrvcs );
But I changed structure in that way, and my bindings in html is not corresponding.
Is there any other way to just remove this sub-sub object, and to preserve a structure as it is?
Here is the
Here's an example that maps a new array of new objects and the filter is set to only include the GroupedServices items where Id == 2000
let res = data.map(({ServiceTypeName, GroupedServices}) =>{
GroupedServices= GroupedServices.filter(({Id}) => Id == 2000);
return {ServiceTypeName,GroupedServices }
})
console.log(res)
<script>
let data =
[
{
"ServiceTypeName": "Type 1",
"GroupedServices": [{
"Id": 1,
"Name": "A"
}, {
"Id": 2,
"Name": "A"
},
{
"Id": 28456,
"Name": "AGSADGJS"
}]
},
{
"ServiceTypeName": "Type 2",
"GroupedServices": [{
"Id": 1203,
"Name": "AHASJ"
}, {
"Id": 2000,
"Name": "AHSJD"
},
{
"Id": 284536,
"Name": "UEHNCK"
}]
}];
</script>

Remove childless elements (except leafs) from nested array

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

Merging an array of objects without overwriting

I am currently with some JSON, which has to be structured in a tree-like hierarchy. The depth of the hierarchy varies a lot, and is therefor unknown.
As it is right now, I have achieved to get an array of objects. Example is below.
[
{
"name": "level1",
"collapsed": true,
"children": [
{
"name": "Level 1 item here",
"id": 360082134191
}
]
},
{
"name": "level1",
"collapsed": true,
"children": [
{
"name": "level2",
"collapsed": true,
"children": [
{
"name": "Level 2 item here",
"id": 360082134751
}
]
}
]
},
{
"name": "level1",
"collapsed": true,
"children": [
{
"name": "Another level 1 item",
"id": 360082262772
}
]
}
]
What I want to achieve is these objects to be merged, without overwriting or replacing anything. Listed below is an example of how I want the data formatted:
[
{
"name": "level1",
"collapsed": true,
"children": [
{
"name": "level2",
"collapsed": true,
"children": [
{
"name": "Level 2 item here",
"id": 360082134751
}
]
},
{
"name": "Level 1 item here",
"id": 360082134191
},
{
"name": "Another level 1 item",
"id": 360082262772
}
]
}
]
How would I achieve this with JavaScript? No libraries is preferred, ES6 can be used though.
Edit:
It is important that the output is an array, since items without children can appear at the root.
I am assuming you need a little help on working with the data. There could be multiple ways to achieve this, here is how would I do.
// data => supplied data
const result = data.reduce ((acc, item) => {
// if acc array already contains an object with same name,
// as current element [item], merfe the children
let existingItem;
// Using a for loop here to create a reference to the
// existing item, so it'd update this item when childrens
// will be merged.
for (let index = 0; index < acc.length; index ++) {
if (acc[index].name === item.name) {
existingItem = acc[index];
break;
}
}
// if existingItem exists, merge children of
// existing item and current item.
// else push it into the accumulator
if (existingItem) {
existingItem.children = existingItem.children.concat(item.children);
} else {
acc.push (item);
}
return acc;
}, []);
I'm assuming you want to group based on the name property in the level 1 object. You could do a simple reduce and Object.values like this:
const input = [{"name":"level1","collapsed":true,"children":[{"name":"Level 1 item here","id":360082134191}]},{"name":"level1","collapsed":true,"children":[{"name":"level2","collapsed":true,"children":[{"name":"Level 2 item here","id":360082134751}]}]},{"name":"level1","collapsed":true,"children":[{"name":"Another level 1 item","id":360082262772}]}]
const merged = input.reduce((r,{name, collapsed, children}) =>{
r[name] = r[name] || {name, collapsed, children:[]};
r[name]["children"].push(...children)
return r;
}, {})
const final = Object.values(merged);
console.log(final)
You could do the whole thing in one line:
const input = [{"name":"level1","collapsed":true,"children":[{"name":"Level 1 item here","id":360082134191}]},{"name":"level1","collapsed":true,"children":[{"name":"level2","collapsed":true,"children":[{"name":"Level 2 item here","id":360082134751}]}]},{"name":"level1","collapsed":true,"children":[{"name":"Another level 1 item","id":360082262772}]}]
const output = Object.values(input.reduce((r,{name,collapsed,children}) => (
(r[name] = r[name] || {name,collapsed,children: []})["children"].push(...children), r), {}))
console.log(output)

Sort-Index from nested JSON with Javascript

How can I recursively add a sort key to an infinite hierarchy like this:
[
{
"id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"name": "A",
"parent_id": null,
"sortNr": 1,
"children": [
{
"id": "07E556EE-F66F-49B5-B5E4-54AFC6A4DD9F",
"name": "A-C",
"parent_id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"sortNr": 3,
"children": []
},
{
"id": "8C63981E-0D30-4244-94BE-658BAAF40EF3",
"name": "A-A",
"parent_id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"sortNr": 1,
"children": [
{
"id": "0BA32F23-A2CD-4488-8868-40AD5E0D3F09",
"name": "A-A-A",
"parent_id": "8C63981E-0D30-4244-94BE-658BAAF40EF3",
"sortNr": 1,
"children": []
}
]
},
{
"id": "17A07D6E-462F-4983-B308-7D0F6ADC5328",
"name": "A-B",
"parent_id": "D41F4D3D-EA9C-4A38-A504-4415086EFFF8",
"sortNr": 2,
"children": []
}
]
},
{
"id": "64535599-13F1-474C-98D0-67337562A621",
"name": "B",
"parent_id": null,
"sortNr": 2,
"children": []
},
{
"id": "1CE38295-B933-4457-BBAB-F1B4A4AFC828",
"name": "C",
"parent_id": null,
"sortNr": 3,
"children": [
{
"id": "D1E02274-33AA-476E-BA31-A4E60438C23F",
"name": "C-A",
"parent_id": "1CE38295-B933-4457-BBAB-F1B4A4AFC828",
"sortNr": 1,
"children": [
{
"id": "76A8259C-650D-482B-91CE-D69D379EB759",
"name": "C-A-A",
"parent_id": "D1E02274-33AA-476E-BA31-A4E60438C23F",
"sortNr": 1,
"children": []
}
]
}
]
}
]
I want to get a sortable index.
For example 0000.0001.0003 or 0001.0003 for node A-C.
The function for leadingZeroes is
function fillZeroes (num) {
var result = ('0000'+num).slice(-4);
if (num===null){
return result
} else {
return '0000';
}
}
It should be sorted by sort number in each level of hierarchy, the sort number should be set newly every time, because I want to do rearrangement by setting it 1,5 to insert it between 1 and 2 (later for drag and drop capability). so 1;1,5;2 should become 1;2;3 and can then be translated to a sort-index like above.
I will also need it for indentation and breadcrumb-stuff.
How do I insert the proper sort-index to each object ?
The question is mainly about the recursion part. I am quite new to JavaScript
Thanks a lot
Based on great answer by #georg. A bit adjusted solution based on sortNr object property.
You can run it straight as is with json being your object. The sort index is written into sortOrder property.
// Mutates the given object in-place.
// Assigns sortOrder property to each nested object
const indexJson = (json) => {
const obj = {children: json};
const format = (xs) => xs.map(x => pad(x, 4)).join('.');
const pad = (x, w) => (10 ** w + x).toString().slice(-w);
const renumber = (obj, path) => {
obj.path = path;
obj.sortOrder = format(path);
obj.children.slice()
.sort((obj1, obj2) => obj1.sortNr - obj2.sortNr)
.forEach((c, n) => renumber(c, path.concat(n+1)));
};
renumber(obj, []);
};
indexJson(json);
console.log(JSON.stringify(json, null, 2));
Basically
let renumber = (obj, path) => {
obj.path = path
obj.children.forEach((c, n) => renumber(c, path.concat(n)))
}
renumber({children: yourData}, [])
this creates a path property, which is an array of relative numbers. If you want to format it in a special way, then you can do
obj.path = format(path)
where format is like
let format = xs => xs.map(pad(4)).join(',')
let pad = w => x => (10 ** w + x).toString().slice(-w)

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