Search deep in array and delete - javascript

I got the following array:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
}
}
]
}
];
The objects directly in the array are the groups. The 1: is the first language, 2: is second etc. The id is stored in every language object (due to the database I'm using). The children array is built the same way as the 'arr' array.
Example of multiple children:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
},
children: [
{
1: {
id: 3,
title: 'test3',
},
children: []
}
]
}
]
}
];
Now I need to delete items from this array. You can have unlimited children (I mean, children can have children who can have children etc.). I have a function which needs an ID parameter sent. My idea is to get the right object where the ID of language 1 is the id parameter. I got this:
function deleteFromArray(id)
{
var recursiveFunction = function (array)
{
for (var i = 0; i < array.length; i++)
{
var item = array[i];
if (item && Number(item[1].ID) === id)
{
delete item;
}
else if (item && Number(item[1].ID) !== id)
{
recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
}
However, I'm deleting the local variable item except for the item in the array. I don't know how I would fix this problem. I've been looking all over the internet but haven't found anything.

This proposal features a function for recursive call and Array.prototype.some() for the iteration and short circuit if the id is found. Then the array is with Array.prototype.splice() spliced.
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function splice(array, id) {
return array.some(function (a, i) {
if (a['1'].id === id) {
array.splice(i, 1)
return true;
}
if (Array.isArray(a.children)) {
return splice(a.children, id);
}
});
}
splice(arr, 2);
document.write('<pre>' + JSON.stringify(arr, 0, 4) + '</pre>');

var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function deleteFromArray(id) {
function recursiveFunction(arr) {
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
if (item && Number(item[1].id) === id) {
arr.splice(i, 1);
} else if (item && Number(item[1].id) !== id) {
item.children && recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
};
deleteFromArray(2);
document.getElementById("output").innerHTML = JSON.stringify(arr, 0, 4);
<pre id="output"></pre>
jsfiddle: https://jsfiddle.net/x7mv5h4j/2/
deleteFromArray(2) will make children empty and deleteFromArray(1) will make arr empty itself.

Related

i need a help how can i treat maximum call stack?

Thanks i fixed some sentence by advice. my code is like that,
i wanna find object with id. but if not, I want to return 'null'
function ha7(arr, id) { // i wanna find object with id
let result = [];
for(let i = 0 ; i < arr.length ; i++) {
if(arr[i].id === id) {
return arr[i] // found id, then return included object
}
else if(Array.isArray(arr[i].children)){ // but , its array
// let ar = ha7(arr[i].children, id)
result.push(...arr[i].children) // i put 'arr[i].children' to variables
}
}
if (result.id === id) {
return result // find object with id in inner
} else {
return ha7(result, id) // cant find. then go ahead!
}
return null // all of none exist id is return null
}
it is testing array.
let input = [
{
id: 1,
name: 'johnny',
},
{
id: 2,
name: 'ingi',
children: [
{
id: 3,
name: 'johnson',
},
{
id: 5,
name: 'steve',
children: [
{
id: 6,
name: 'lisa',
},
],
},
{
id: 11,
},
],
},
{
id: '13',
},
];
output = ha7(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = ha7(input, 99);
console.log(output); // --> null
I tried a lot of trial, like that. i wanna know.
how can i treat maximum call stack ?
and i wanna return 'null' value.
function ha7(arr, id) { // i wanna find object with id
let result = [];
for(let i = 0 ; i < arr.length ; i++) {
if(arr[i].id === id) {
return arr[i] // found id, then return included object
}
else if(Array.isArray(arr[i].children)){ // but , its array
// let ar = ha7(arr[i].children, id)
result.push(...arr[i].children) // i put 'arr[i].children' to variables
}
}
if (result.id === id) {
return result // find object with id in inner
} else {
return ha7(result, id) // cant find. then go ahead!
}
return null // all of none exist id is return null
}
let input = [
{
id: 1,
name: 'johnny',
},
{
id: 2,
name: 'ingi',
children: [
{
id: 3,
name: 'johnson',
},
{
id: 5,
name: 'steve',
children: [
{
id: 6,
name: 'lisa',
},
],
},
{
id: 11,
},
],
},
{
id: '13',
},
];
output = ha7(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = ha7(input, 99);
console.log(output); // --> null
This code is the problem:
if (result.id === id) {
return result // find object with id in inner
} else {
return ha7(result, id) // cant find. then go ahead!
}
Two lines above this you initialize result as an array. Then in this conditional test you treat the array result as if it were an object. So, since result.id does not equal id, the else condition recurses for ever and ever.
I've taken a different, more functional approach to the task.
filter the array on the id
If there is a length then at least one was found
Return the first one
Next filter out all the objects with children
Then create an array (with .map() that only includes the children
This will create an array of arrays, so must flatten it
If there are no children, then id was not found
Return null
Recurse the children
let input=[{id:1,name:"johnny"},{id:2,name:"ingi",children:[{id:3,name:"johnson"},{id:5,name:"steve",children:[{id:6,name:"lisa"}]},{id:11}]},{id:"13"}];
function ha7(arr, id) {
let found = arr.filter(o => o.id === id);
if (found.length) return found[0]; // return first match
let children = arr.filter(o=>!!o.children).map(c=>c.children).flat();
if(!children.length) return null;
return ha7(children, id);
}
output = ha7(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = ha7(input, 99);
console.log(output); // --> null

Build list of ancestors from nested array

The nested array looks like this:
var arr = [{
id: 2,
name: 'a',
children: []
}, {
id: 5,
name: 'b',
children: [{
id: 14,
name: 'b2'
}]
}, {
id: 15,
name: 'd',
children: []
}];
How can I make a list of ancestor elements, from any given element?
For example, if given element has id: 14 the list should return only the parent:
[{
id: 5,
name: 'b',
children: [...]
}]
I'm looking to replicate a "breadcrumb" navigation
You could handover an object which is the parent and search recursive for the wanted id.
function getParent(object, id) {
var result;
(object.children || []).some(o => result = o.id === id ? object : getParent(o, id));
return result;
}
var array = [{ id: 2, name: 'a', children: [] }, { id: 5, name: 'b', children: [{ id: 14, name: 'b2' }] }, { id: 15, name: 'd', children: [] }];
console.log(getParent({ children: array }, 14));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you like to hand over the array, you could take a nested approach with recursive function.
function getParent(children, id) {
function iter(object) {
var result;
(object.children || []).some(o => result = o.id === id ? object : iter(o));
return result;
}
return iter({ children });
}
var array = [{ id: 2, name: 'a', children: [] }, { id: 5, name: 'b', children: [{ id: 14, name: 'b2' }] }, { id: 15, name: 'd', children: [] }];
console.log(getParent(array, 14));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If we can assume that only two levels exist (parents and children, not children of children) then the following function findAncestor() does what you need. It iterates over all parent elements and checks if they have a child with the relevant ID.
function findAncestor(id) {
for (let i = 0; i < arr.length; i++) {
let obj = arr[i];
if (obj.hasOwnProperty('children')) {
if (obj.children.length > 0) {
for (let j = 0; j < obj.children.length; j++) {
if (obj.children[j].id === id) {
return obj;
}
}
}
}
}
return null;
}
console.info(findAncestor(14));
If you need to handle the case that a child with same ID can occur in several parents, you should use a result array and add all found results to it before returning it in the end.
You can try this way,
var arr = [{
id: 2,
name: 'a',
children: []
}, {
id: 5,
name: 'b',
children: [{
id: 14,
name: 'b2'
}]
}, {
id: 15,
name: 'd',
children: []
}];
function getAncestor(obj,id,ancestor){
if(obj.id===id){
console.log(ancestor);
}
else
if(obj&& obj.children && obj.children.length)
obj.children.forEach(item=>this.getAncestor(item,id,obj));
}
arr.forEach(o=>getAncestor(o,14,{}));
The Depth First Search Algorithm - get parent node is:
function getParentNodeByKey(obj, targetId, paramKey) {
if (obj.children) {
if (obj.children.some(ch => ch[paramKey] === targetId))
return obj;
else {
for (let item of obj.children) {
let check = this.getParentNodeByKey(item, targetId, paramKey)
if (check) {
return check;
}
}
}
}
return null
}
and code to test:
var arr = [{
id: 2,
name: 'a',
children: []
}, {
id: 5,
name: 'b',
children: [{
id: 14,
name: 'b2'
}]
}, {
id: 15,
name: 'd',
children: []
}];
let parentElement;
const valueToFind = 14;
const desiredkey = 'id';
for (let i = 0; i < arr.length; i++) {
parentElement = getParentNodeByKey(arr[i], valueToFind, desiredkey);
if(parentElement)
break;
}
console.log(`The parent element is:`, parentElement);

Move item anywhere in a nested array

I've managed to copy the intended object into the intended location (my code is below), but how do I move it? So it will not exist in the original location any more.
So in my example, I want to take the object with id of 14 (very bottom of the object) and move it into the children of the object with id of 3 (towards the top).
I know I need to modify this line: item.children.push(itemToMove) in my moveItem function, but some reason I can't think of it.
Also sorry about the very big/nested object, I wanted to make sure to cover a deeply nested object.
const myObj = [
{
id: 1,
name: '1',
children: [
{
id: 2,
name: '2',
children: [
{
id: 3,
name: '3',
children: []
}
]
},
{
id: 4,
name: '4',
children: [
{
id: 5,
name: '5',
children: [
{
id: 6,
name: '6',
children: [
{
id: 7,
name: '7',
children: []
}
]
}
]
}
]
},
]
},
{
id: 8,
name: '8',
children: [
{
id: 9,
name: '9',
children: [
{
id: 10,
name: '10',
children: []
}
]
},
{
id: 11,
name: '11',
children: [
{
id: 12,
name: '12',
children: [
{
id: 13,
name: '13',
children: [
{
id: 14,
name: '14',
children: []
}
]
}
]
}
]
},
]
}
]
let itemToMove = {
id: 14,
name: '14',
children: []
}
// move item, return updated obj
function moveItem(itemToMove, obj, parentId) {
for (let i=0;i<obj.length;i++) {
const value = obj[i];
const item = search(obj[i], parentId);
if (item) {
item.children.push(itemToMove); // pushed into children, but need to move not duplicate in
break;
}
}
function search(obj, id) {
if (obj.id === id) {
return obj;
}
for (let i=0;i<obj.children.length;i++) {
const possibleResult = search(obj.children[i], id);
if (possibleResult) {
return possibleResult;
}
}
}
return obj;
};
console.log(moveItem(itemToMove, myObj, 3))
I would probably do something like this, taking into account that if the insert fails, you should have some kind of way to re-instate the data. I also used ES6 which is different to your code, but it gives you some kind of idea.
let parent
function removeItem (obj, itemToFind) {
// Loop the object
obj.find((e, index) => {
// If the id's match remove from the parent if it exists otherwise from the object as its at root level
if (e.id === itemToFind.id) {
if (parent) {
parent.children.splice(index, 1)
} else {
obj.splice(index, 1)
}
// break the loop once returned true. Change find to forEach to remove all instances with id if allowing multiples
return true
}
// recurse
else if (e.children && e.children.length > 0) {
parent = e
return removeItem(e.children, itemToFind)
}
})
}
// move item, return updated obj
function moveItem (itemToMove, obj, parentId) {
for (let i = 0; i < obj.length; i++) {
const value = obj[i]
const item = search(obj[i], parentId)
if (item) {
item.children.push(itemToMove) // pushed into children, but need to move not duplicate in
break
}
}
function search (obj, id) {
if (obj.id === id) {
return obj
}
for (let i = 0; i < obj.children.length; i++) {
const possibleResult = search(obj.children[i], id)
if (possibleResult) {
return possibleResult
}
}
}
return obj
};
removeItem(myObj, itemToMove)
moveItem(itemToMove, myObj, 3)

Find index of a dynamic multidimensional array/json with matches id

thanks for checking,
I have a dynamic array which will contain multiple item/object.
I want the index number of this array, if a provided id matches with one of its contained
But Because it is a dynamically generated array/json
it can have any amount of multidimensional array inside child items and so on and so forth.
so is there any way to find the index number with matches id.
var data = [
{
id:1,
child:[
{
id: 2,
child: [
{
id: 3,
child: []
},
{
id:4,
child:[
{
id:44,
child:[
{
id:55,
child:[]
}
]
}
]
}
]
},
{
id:5,
child:[
{
id:6,
child:[]
}
]
}
]
}
]
Suppose i want to get the index of array where id is equal to 4.
I need to develop a logic/function which will return -> data[0]['child'][0]['child'][1]
Do it recursively
function findId(obj, id, currentPath = "") {
// Go through every object in the array
let i = 0;
for (let child of obj) {
// If id matches, return
if (child.id == id) return currentPath + `[${i}]`;
// Else go through the children, if we find anything there, return it
let next = findId(child.child, id, currentPath + `[${i}]['child']`);
if (next) return next;
i++;
}
// We didn't find anything
return null;
}
You could take a complete dynmaic approach with knowing some keys.
function findPath(object, id) {
var path;
if (!object || typeof object !== 'object') return;
if (object.id === id) return [];
Object.entries(object).some(([k, o]) => {
var temp;
if (temp = findPath(o, id, path = [])) {
path = [k, ...temp];
return true;
}
});
return path;
}
var data = [{ id: 1, child: [{ id: 2, child: [{ id: 3, child: [] }, { id: 4, child: [{ id: 44, child: [{ id: 55, child: [] }] }] }] }, { id: 5, child: [{ id: 6, child: [] }] }] }];
console.log(findPath(data, 44));
.as-console-wrapper { max-height: 100% !important; top: 0; }

JavaScript: Setting Nested object value by ID

I would like to know what is the best way to update the member of the multilevel object collection in JavaScript
Here is the simplified version of my collection:
this.Steps = [
{ id: 1, text: "test", childSteps:
[
{ id: 2, text: "test"},
{ id: 3, text: "test"},
{ id: 4, text: "test", childSteps:
[
{ id: 10, text: "test"},
{ id: 11, text: "test"}
]}
},
{ id: 5, text: "test"},
{ id: 6, text: "test"},
{ id: 7, text: "test"},
{ id: 8, text: "test"},
{ id: 9, text: "test"}
]
}
];
The ideal would be to have a function to be called like:
updateObjectByID(11, 'string to be set');
This is easy to be done when we have only 1 level of objects. But when using recursion on multilevel collection its getting much harder.
I'm currently using a function that is parsing the whole collection and building string like :
this.Steps[0].childSteps[3].childSteps[1].text == "string to be set"
and then I do eval() on that string.
I'm sure there might be a much cleaner solution.
Using eval is making my class impossible to compress btw.
Any help would be greatly appreciated.
Thanks in advance
You can build a map of objects by id for direct access:
var map = {};
(function recurse(steps) {
for (var i=0; i<steps.length; i++) {
var step = steps[i];
map[ step.id ] = step;
if ("childSteps" in step)
recurse(step.childSteps);
}
})(this.Steps);
function updateObjectByID(id, string) {
map[id].text = string;
}
A comment on your code: You overcomplified a lot. When the condition if(obj.id == objId) is met, you have your obj reference already!!! Now there is absolutely no need to search for a path to it, build a string from that, and eval it. Just assign directly to its property!
function(steps, objId, value, whatParam, command) {
if (typeof whatParam == 'undefined')
whatParam = 'selected';
$.each(steps, function(index, obj){
if(obj.id == objId) {
// removed a lot of unecessary stuff
// not sure, looks like "value" was evaled sometimes as well
obj[whatParam] = value;
} // insert an "else" here if ids are unique
if (typeof obj.childSteps != 'undefined') {
this.setupObjectState(obj.childSteps, objId, value, whatParam, command);
}
});
}
Since you call updateObjectById() with an ID, the ID has to be unique. Why donĀ“t you change your structure like this then:
this.Steps = [
{ id: 1, text: "test" },
{ id: 2, parent: 1, text: "test" },
{ id: 3, parent: 1, text: "test" },
{ id: 4, parent: 1, text: "test" },
{ id: 10, parent: 4, text: "test" },
{ id: 11, parent: 4, text: "test" },
{ id: 5, parent: 1, text: "test"},
{ id: 6, parent: 1, text: "test"},
{ id: 7, parent: 1, text: "test"},
{ id: 8, parent: 1, text: "test"},
{ id: 9, parent: 1, text: "test"}
]
This way you can easily update your entries like this:
function updateObjectByID(id, text) {
for(var i = 0; i < this.Steps.length; i++) {
if(this.Steps[i].id === id) {
this.Steps[i].text = text;
break;
}
}
}
With the extra attribute parent you can still easily get all the children of an element like this:
function getChildrenByID(id) {
var array = [];
for(var i = 0; i < this.Steps.length; i++) {
if(this.Steps[i].parent === id) {
array.push(this.Steps[i]);
}
}
return array;
}
You must recursively descend your tree structure searching for the object with the target "id" and replace its text. For example:
function updateObjectByID(obj, id, text) {
if (!obj) return;
if (obj.id === id) {
obj.text = text;
} else if ((typeof(obj)==='object') && (obj.constructor===Array)) {
for (var i=0; i<obj.length; i++) {
updateObjectByID(obj[i], id, text);
}
} else if (obj.childSteps) {
updateObjectByID(obj.childSteps, id, text);
}
}
updateObjectByID(this.Steps, 11, 'String to be set');
However, this solution can be optimized by tree pruning.
function updateObjectByID(id, text) {
(function setText(steps) {
for(var i = 0; i < steps.length; i++){
var step = steps[i];
if(step.id === id){
step.text = text;
}
if ("childSteps" in step){
setText(step.childSteps);
}
}
})(this.Steps);
}
Here is demo. http://jsfiddle.net/qDWX8/

Categories

Resources