Recursive Tree insertion in Javascript - javascript

I try to write insert into a tree data structure recursively in javascript with tree-node, but don't get it working.
So my question would be, how to approach the issue.
this is my data:
[ { id: 'a', children: [ 'b', 'c' ] },
{ id: 'b', children: [ '' ] },
{ id: 'c', children: [ 'b', 'd' ] },
{ id: 'd', children: [ 'b' ] } ]
I want that showing up in a tree like the following:
a
/\
b c
/\
b d
\
b
Edit: Added code
I Thought i could do something like this, but that doesn't work... and of course has high complexity because of the nested forEach:
var Node = require("tree-node");
var testarray =
[
{ id: 'a', children: [ 'b', 'c' ] },
{ id: 'b', children: [ '' ] },
{ id: 'c', children: [ 'b', 'd' ] },
{ id: 'd', children: [ 'b' ] }
]
function appendChildRecursive(parent) {
var childnode = new Node()
var data = parent.data("children")
testarray.forEach(function(item){
if(data !== undefined) {
data.forEach(function (child) {
if (item.id == child) {
childnode.data("id", child).data("children", item.children)
childnode = appendChildRecursive(childnode)
parent.appendChild(childnode)
}
})
}
})
return parent
}
var root = new Node();
root.data("id",testarray[0].id).data("children",testarray[0].children)
root=appendChildRecursive(root)

You could use a hash table for the last inserted nodes and keep the reference to the last nodes by overwriting the reference.
var data = [{ id: 'a', children: ['b', 'c'] }, { id: 'b', children: [] }, { id: 'c', children: ['b', 'd'] }, { id: 'd', children: ['b'] }],
tree = function (array) {
var nodes = Object.create(null),
r = {};
array.forEach(function (a) {
if (!nodes[a.id]) {
nodes[a.id] = { id: a.id, children: [] };
r = nodes[a.id];
}
a.children.forEach(function (b) {
nodes[b] = { id: b, children: [] };
nodes[a.id].children.push(nodes[b]);
});
});
return r;
}(data);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Your data structure is wrong.
Each 'leaf' should contain reference to the 'left' and 'right' element.
for example:
const first = { id: 'a', left: null, right: null };
const second = { id: 'b', left: null, right: first };
// etc...
The children approach would be more suitable for graph.
But you still have to store references, not ids.

Related

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

Unflatten array to Tree without parent id but with level

I'm a bit stuck with something implying recursion. I am receiving data
from an API. It looks like this:
const input = [
{ id: 'a', level: 0 },
{ id: 'b', level: 1 },
{ id: 'c', level: 1 },
{ id: 'd', level: 2 },
{ id: 'e', level: 1 },
{ id: 'f', level: 0 },
];
and I need something like
const out = [
{ id: 'a', nodes: [
{ id: 'b', nodes: [] },
{ id: 'c', nodes: [
{ id: 'd', nodes: [] },
] },
{ id: 'e', nodes: [] },
] },
{ id: 'f', nodes: [] },
];
How would you achieve that in an elegant way such as out = f(input) ?
I feel we can do a recursive nest method through a reduce but I did not manage to get it right :)
Thanks in advance!
You could use a helper array for the levels with the latest array/nodes property from the object.
const
input = [{ id: 'a', level: 0 }, { id: 'b', level: 1 }, { id: 'c', level: 1 }, { id: 'd', level: 2 }, { id: 'e', level: 1 }, { id: 'f', level: 0 }],
result = [],
levels = [result];
input.forEach(({ id, level }) =>
levels[level].push({ id, nodes: levels[level + 1] = [] })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You may try out like,
function makeObject(id){
return { id: id, nodes:[] };
}
function addObjectToNodes(array, id, node){
array.map(a => {
if(a.id === id)
a.nodes.push(node);
});
}
const nodes = [];
nodes.push(makeObject('a'));
nodes.push(makeObject('f'));
addObjectToNodes(nodes, 'a', makeObject('b'));
addObjectToNodes(nodes, 'a', makeObject('c'));
addObjectToNodes(nodes, 'a', makeObject('d'));
addObjectToNodes(nodes, 'a', makeObject('e'));
console.log(nodes);

how to transform or sort array against different array?

const r = ['a', 'b', 'c', 'l', 'p'];
const arr = [{
name: "ss3",
id: 'c'
}, {
name: "ss2",
id: 'b'
}, {
name: "ss4",
id: 'p'
}, {
name: "ss1",
id: 'a'
}]
var newArray =arr.map((i)=>{
let e = r[i];
if(i.id===e){
return i
}
})
console.log(newArray)
Expected output
const arr = [{
name: "ss1",
id: 'a'
}, {
name: "ss2",
id: 'b'
}, {
name: "ss3",
id: 'c'
}, {
name: "ss4",
id: 'p'
}
]
Given two arrays r and arr, I wish to sort arr with respect to r, i.e. in alphabetical order by id.
https://jsbin.com/yitijiboso/edit?html,js,output
I think this might be a concise (although not very performant) way to achieve the desired output:
const arr1 = ['a', 'b', 'c', 'l', 'p'];
const arr2 = [
{
name: "ss3",
id: 'c'
},
{
name: "ss2",
id: 'b'
}, {
name: "ss4",
id: 'p'
},
{
name: "ss1",
id: 'a'
}
];
arr2.sort((a, b) => arr1.indexOf(a.id) - arr1.indexOf(b.id));
console.log(arr2);
Easy:
make a map from main 'arr' keyBy 'id' https://www.npmjs.com/package/lodash.keyby
loop across 'r', if key exist in new map, get value and push to new array
const arrMap = _.keyBy(arr, 'id');
let newR = [];
r.forEach( key => {
if ( arrMap[key] ) {
newR.push( arrMap[key] );
}
} );
console.log( 'new array', newR );
Taking a clue from #Petr Broz, here's my suggestion:
const r = ['a', 'b', 'c', 'l', 'p'];
const arr = [
{
name: "ss3",
id: 'c'
},
{
name: "ss2",
id: 'b'
}, {
name: "ss4",
id: 'p'
},
{
name: "ss1",
id: 'a'
}
];
arr.sort((a, b) => r.indexOf(a.id) > r.indexOf(b.id));
console.log(arr);
Main difference is that this code utilizes the arrays as named by the OP and uses a greater than comparison operator. However, if you just want to have the array arr sorted in alphabetical order you don't need to compare it with array r:
const arr = [
{
name: "ss3",
id: 'c'
},
{
name: "ss2",
id: 'b'
}, {
name: "ss4",
id: 'p'
},
{
name: "ss1",
id: 'a'
}
];
arr.sort(function(a, b)
{
if (a.id > b.id) {
return 1;
}
else
if (a.id < b.id) {
return -1;
}
else
{
return 0;
}
});
console.log(arr);
Note, in this example the return values are numeric instead of boolean values which would be helpful if the array to be sorted were to have duplicate values.

Turn flat array into nested array using javascript

I am looking to turn this flat array into an nested array but it keeps returning as empty. Not sure how to solve this one or what I am missing but this is driving me crazy.
Flat array:
var names =[
{ name: 'b', parent: 'Brown' },
{ name: 'a', parent: 'Brown' },
{ name: 'h', parent: 'Green' },
{ name: 'c', parent: 'Green' },
];
Desired output of array:
[{
name: 'Brown',
children: [{
name: 'a',
children: []
},
{
name: 'b',
children: []
}
]
}, {
name: 'Green',
children: [{
name: 'h',
children: []
},
{
name: 'c',
children: []
}
]
}
}]
Js:
function getNestedChildren(arr, parent) {
var children = [];
for(var i =0; i < arr.length; ++i) {
if(arr[i].parent == parent) {
var grandChildren = getNestedChildren(arr, arr[i].name)
if(grandChildren.length) {
arr[i].children = grandChildren;
}
children.push( arr[i]);
}
}
return children;
}
var nest = getNestedChildren(names, names.parent);
console.log( nest);
var names =[
{ name: 'b', parent: 'Brown' },
{ name: 'a', parent: 'Brown' },
{ name: 'h', parent: 'Green' },
{ name: 'c', parent: 'Green' },
{ name: 'j', parent: 'Brown' }
];
function groupBy(arr, f) {
return arr.reduce((r, v, i, a, k = f(v)) => ((r[k] || (r[k] = [])).push(v), r), {});
}
function nestArray(array){
var newArray=[],
resultGrouped = groupBy(names, function(c) {
return c.parent
});
for (var key in resultGrouped){
var item=resultGrouped[key];
newArray.push({
name:key,
children:item.map(function(map){
delete map.parent;
map.children=[];
return map;
})
});
}
return newArray;
}
console.log(nestArray(names));
You can create a new object, for each item assign an array to a key with the parent name, and concatenate the item to that array
var names =[
{ name: 'b', parent: 'Brown' },
{ name: 'a', parent: 'Brown' },
{ name: 'h', parent: 'Green' },
{ name: 'c', parent: 'Green' },
];
const getGroup=(groups, parent) => {
let group = groups.find(g=>g.parent===parent);
if(!group){
group=({parent,children:[]});
groups.push(group);
}
return group;
}
let grouped = []
names.forEach(item=> getGroup(grouped,item.parent).children.push(item))
console.log(grouped)
So for starters, you need to loop through the names and send each parent to your getNestedChildren() function.
var names =[
{ name: 'b', parent: 'Brown' },
{ name: 'a', parent: 'Brown' },
{ name: 'h', parent: 'Green' },
{ name: 'c', parent: 'Green' },
];
var nest = [];
for (var i = 0; i < names.length; i++) {
nest.push(getNestedChildren(names, names[i].parent));
}
Also your getNestedChildren() is currently trying to modify and send back the old names array. I'd suggest creating a new object instead and send back the object with the children on it.
function getNestedChildren(arr, parent) {
var children = [];
var parentObj = {};
parentObj.name = parent;
for(var i =0; i < arr.length; ++i) {
if(arr[i].parent == parent) {
children.push(getNestedChildren(arr, arr[i].name));
}
}
parentObj.children = children;
return parentObj;
}

Javascript - Nested parent/child array recursive function that flattens parent only

I have a multidimensional nested parent/child array as follows:
{
parent: {
name: "foo"
},
children: [
{
parent: {
name: "asdf",
},
children: []
},
{
parent: {},
children: []
},
{
parent: {},
children: []
},
...
]
}
What I want to do is apply a recursive function on this array, to flatten only the parent property and keep children as-is, in order to get something like this:
{
name: "foo",
children: [
{
name: "asdf",
children: []
},
{
children: []
},
{
children: []
},
...
]
}
I tried using underscore.js but I am unable to find a recursive function that could do the trick. Any thoughts?
You could use an iterative and recursive approach by iterating the array and updating the wanted properties.
If a children is found, iterate again.
var array = [{ parent: { name: 'a', surname: 'b', }, children: [{ parent: { name: 'c', surname: 'd', }, children: [{ parent: { name: 'e', surname: 'f' } }], childrenName: 'v', childrenSurname: 'w' }], childrenName: 'x', childrenSurname: 'y' }];
array.forEach(function iter(object) {
if (object.parent) {
object.name = object.parent.name;
object.surname = object.parent.surname;
delete object.parent;
}
object.children && object.children.forEach(iter);
});
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
hope this recurse function is what you need
const recurse = (data) => {
for(let i = 0, l = data.length; i < l; i++){
if(data[i]['parent']){
data[i]['name'] = data[i]['parent']['name']
delete data[i]['parent']
}
if(data[i]['children']) recurse(data[i]['children'])
}
}

Categories

Resources