Add Sequential Number To Javascript Objects In Nested Object Tree - javascript

I have an object like the one below and am looking for a way to add one property to each sub object in the tree. That is, I want to add a number to the object that counts up from 0 to n. I know I can traverse through the object with a recursive function but because of variable scope I'm not able to use a simple incremented variable to count up as I go through the tree.
Current Object:
var tree = [
{
name: 'a',
children: []
},{
name: 'b',
children: [
{
name: 'c',
children: []
}
]
},
{
name: 'd',
children: [
{
name: 'e',
children: [
{
name: 'f',
children: []
},
{
name: 'g',
children: []
}
]
}
]
}
];
Desired Object:
var tree = [
{
name: 'a',
number: 0,
children: []
},{
name: 'b',
number: 1,
children: [
{
name: 'c',
number: 2,
children: []
}
]
},
{
name: 'd',
number: 3,
children: [
{
name: 'e',
number: 4,
children: [
{
name: 'f',
number: 5,
children: []
},
{
name: 'g',
number: 6,
children: []
}
]
}
]
}
];

You just need to set counter variable outside of recursive function and increment it before you loop object.
var tree = [{"name":"a","children":[]},{"name":"b","children":[{"name":"c","children":[]}]},{"name":"d","children":[{"name":"e","children":[{"name":"f","children":[]},{"name":"g","children":[]}]}]}];
function addNumber(input) {
var counter = 0;
function rec(data) {
data.forEach(function(e) {
if (typeof e == 'object' && !Array.isArray(e)) {
e.number = counter++;
for (var p in e) {
if (typeof e[p] == 'object') rec(e[p])
}
}
})
}
rec(input)
}
addNumber(tree);
console.log(tree)

The problem you raise with the counter scope can be solved by defining the counter in a closure, within which you also have your recursive function.
Here is an ES6 function that does that:
function numberNodes(tree, n = 0) {
return (function recurse(children) {
return children.map( node => Object.assign({}, node, {
number: n++,
children: recurse(node.children)
}) );
})(tree);
}
// Sample data
var tree = [{ name: 'a', children: []},
{ name: 'b', children:
[{ name: 'c', children: []}]},
{ name: 'd', children:
[{ name: 'e', children:
[{ name: 'f', children: []}, { name: 'g', children: []}]}]}];
// Return tree with numbers added:
tree = numberNodes(tree);
// Output result
console.log(tree);
Note that this function does not mutate the tree you pass it, only the return value has the added properties. So this is a functional programming way of doing it.

Related

How to add elements to array in object property

I have the following array and object I would like to 'match'
const items = [
{ key: 1, name: 'A', owner: 'Alex', },
{ key: 2, name: 'B', owner: 'Barb', },
{ key: 3, name: 'C', owner: 'John', },
{ key: 4, name: 'D', owner: 'Barb', },
{ key: 5, name: 'E', owner: 'Alex', },
];
const owners = {
'Alex': { 1: [], 5: [] },
'John': { 3: [], },
'Barb': { 2: [], 4: [] },
}
I would like to have the following end result:
const ownersWithName = {
'Alex': [{ key: 1, name: 'A', }, { key: 5, name: 'E' }],
'Barb': [{ key: 2, name: 'B', }, { key: 4, name: 'D' }],
'John': [{ key: 3, name: 'C', }, ],
}
So far my solution is this:
function matchOwners (items, owners) {
const ownersWithName = {};
for (const item of items) {
if (owners[item.owner]) {
if (ownersWithName[item.owner]) {
ownersWithName[item.owner] = [ ...ownersWithName[item.owner], item];
} else {
ownersWithName[item.owner] = [item];
}
}
}
return ownersWithName;
}
This solution works, but i feel it's too verbose. i tried to use the spread operator without the if condition, but this needs the array to exist already, otherwise i get the error ownersWithName[item.owner] is not iterable. Is there a better way to do this?
Something like (completely untested):
ownersWithName = items.reduce((result, item) => {
if (owners[item.owner]) {
if (!(item.owner in result)) {
result[item.owner] = [];
}
result[item.owner].push({key: item.key, name: item.name});
}
return result;
}, {})
You can also simply achieve this by using Array.forEach() along with the Object.keys() method.
Live Demo (Descriptive comments has been added in the below code snippet) :
// Input array
const items = [
{ key: 1, name: 'A', owner: 'Alex', },
{ key: 2, name: 'B', owner: 'Barb', },
{ key: 3, name: 'C', owner: 'John', },
{ key: 4, name: 'D', owner: 'Barb', },
{ key: 5, name: 'E', owner: 'Alex', },
];
// Input object which should be used to match.
const owners = {
'Alex': { 1: [], 5: [] },
'John': { 3: [], },
'Barb': { 2: [], 4: [] },
};
// Declare an object which will store the final result.
const resultObj = {};
// Iterating over an items array to manipulate the data and make the final result set.
items.forEach(obj => {
resultObj[obj.owner] = !resultObj[obj.owner] ? [] : resultObj[obj.owner];
Object.keys(owners[obj.owner]).forEach(key => {
if (key == obj.key) {
resultObj[obj.owner].push({
key: obj.key,
name: obj.name
});
}
});
});
// Final output
console.log(resultObj);

Move items to front of array if they exists in another array, based on key

I'm sort an array based on the keys in another array. If they find a match, it would move those items to the front of the array. But I can't think of a clean way to do this.
let myArray = [
{ id: 'a', name: 'Mal' },
{ id: 'b', name: 'Wash'},
{ id: 'c', name: 'Inara'},
{ id: 'd', name: 'Jayne'},
]
let sortArray = [
{ id: 'b' },
{ id: 'c' },
{ id: 'x' },
]
/* Expected result
myArray = [
{ id: 'b', name: 'Wash'},
{ id: 'c', name: 'Inara'},
{ id: 'a', name: 'Mal' },
{ id: 'd', name: 'Jayne'},
]
/*
Does anyone know a way to do this without just looping through it a bunch of times? Thanks
You could create a Map which maps each id in sortArray to its index. Use this priority map object to sort the first array.
const array = [{ id: 'a', name: 'Mal' }, { id: 'b', name: 'Wash'}, { id: 'c', name: 'Inara'}, { id: 'd', name: 'Jayne'}],
sortArray = [{ id: 'b' }, { id: 'c' }, { id: 'x' }],
map = new Map( sortArray.map((o, i) => [o.id, i]) )
array.sort((a,b) =>
( map.has(b.id) - map.has(a.id) ) || ( map.get(a.id) - map.get(b.id) )
)
console.log(array)
You could take an object ffor the wanted order of the items and a default value for unknown items.
let array = [{ id: 'a', name: 'Mal' }, { id: 'b', name: 'Wash'}, { id: 'c', name: 'Inara'}, { id: 'd', name: 'Jayne'}],
sortArray = [{ id: 'b' }, { id: 'c' }, { id: 'x' }],
order = Object.fromEntries(sortArray.map(({ id }, i) => [id, i + 1]));
array.sort(({ id: a }, { id: b }) =>
(order[a] || Number.MAX_VALUE) - (order[b] || Number.MAX_VALUE)
);
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to select multiple specific property with distinct value from javascript array

Suppose I have a Javascript array like below.
const data = [
{ group: 'A', name: 'SD', testid:1},
{ group: 'B', name: 'FI',testid:2 },
{ group: 'A', name: 'MM', testid:1 },
{ group: 'B', name: 'CO', testid:2 },
{ group: 'A', name: 'QW', testid:1 }
];
I want to get two specific properties(group and testid).
I would like to retrieve unique values for those properties in my end result.
So my end result will be
{group:A,testid:1},{group:B,testid:2}
What I have tried so far is below.
data.map(item=>item.group).
But this will give me only one property and without any distinct values
How can I achieve this using latest ecmascript syntax in Javascript
you can reduce the array and check every time if the pair exists:
data.reduce((prev, el) =>{
if(prev.some(o => o.group == el.group && o.testid == el.testid))
return prev;
return [...prev, {group:el.group, testid:el.testid}]
}, [])
const data = [
{ group: 'A', name: 'SD', testid:1},
{ group: 'B', name: 'FI',testid:2 },
{ group: 'A', name: 'MM', testid:1 },
{ group: 'B', name: 'CO', testid:2 },
{ group: 'A', name: 'QW', testid:1 }
];
let result = data.reduce((prev, el) =>{
if(prev.some(o => o.group == el.group && o.testid == el.testid))
return prev;
return [...prev, {group:el.group, testid:el.testid}]
}, []);
console.log(result);
You can loop over it and get the desired result.
result = []
data.forEach(x=>{
if(!result.some(y=>y.group===x.group && x.testid===y.testid)){
result.push({group:x.group,testid:x.testid});
}
});
Use forEach loop and build an object with keys as uniq_id.
After the traverse, return Object.values of the above object.
const convert = (arr) => {
const res = {};
arr.forEach(({group, testid}) => {
// change uniq_id based on requirement
const uniq_id = `${group}-${testid}`;
res[uniq_id] = { group, testid};
});
return Object.values(res);
}
const data = [
{ group: 'A', name: 'SD', testid:1},
{ group: 'B', name: 'FI',testid:2 },
{ group: 'A', name: 'MM', testid:1 },
{ group: 'B', name: 'CO', testid:2 },
{ group: 'A', name: 'QW', testid:1 }
];
console.log(convert(data));

Search nested array of objects and return the full path to the object

Say I have the following object:
const pages = [
{
name: 'a',
id: '1',
pages: [
{
name: 'b',
id: '1.1',
pages: []
},
{
name: 'c',
id: '1.2',
pages: [
{
name: 'd',
id: '1.2.1',
pages: []
}
]
},
]
},
{
name: 'e',
id: '2',
pages: []
}
]
I'd like to perform a function on this nested object that will return the 'path' to the object I'm searching for.
So something like
getPath(pages, '1.2.1')
will return:
[
{
name: 'a',
id: '1'
},
{
name: 'c',
id: '1.2'
},
{
name: 'd'
id: '1.2.1'
}
]
Here's what I have so far. Its just a recursive function to find the object I want. I'm stuck on how to build the path as I'm traversing through the object though.
const pages = [
{
name: 'a',
id: '1',
pages: [
{
name: 'b',
id: '1.1',
pages: []
},
{
name: 'c',
id: '1.2',
pages: [
{
name: 'd',
id: '1.2.1',
pages: []
}
]
},
]
},
{
name: 'e',
id: '2',
pages: []
}
]
function getPath(pages, pageId) {
let path = [];
for (let i = 0; i < pages.length; i++) {
const item = search(pages[i], pageId);
// build path here?
if (item) {
return item;
}
}
}
function search(obj, id) {
if (obj.id === id) {
return obj;
}
for (let i = 0; i < obj.pages.length; i++) {
const possibleResult = search(obj.pages[i], id);
if (possibleResult) {
return possibleResult;
}
}
}
console.log(getPath(pages, '1.2.1'))
You can use this alternative for getting the path, it's a recursive approach and uses an array called path as param in order to track the visited levels.
Assuming the ID's are uniques regardless of the location/level.
const pages = [ { name: 'a', id: '1', pages: [ { name: 'b', id: '1.1', pages: [] }, { name: 'c', id: '1.2', pages: [ { name: 'd', id: '1.2.1', pages: [] } ] }, ] }, { name: 'e', id: '2', pages: [] }];
const loop = (arr, target, index, path) => {
if (arr[index].id === target) {
path.push({name: arr[index].name, id: arr[index].id});
} else if (arr[index].pages.length) {
path.push({name: arr[index].name, id: arr[index].id});
arr[index].pages.forEach((_, i, a) => {
loop(a, target, i, path);
});
if (path[path.length - 1].id === arr[index].id) path.pop();
}
};
let getPath = (arr, target) => {
let path = [];
arr.forEach((_, i, a) => loop(a, target, i, path));
return path;
};
console.log(getPath(pages, '1.2.1'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Sum children values and save result to parent in n-ary tree in Javascript

I have a tree in Javascript with the following structure, a simple example:
tree: [
{
id: 'A'
parents: []
children: ['B']
value: 1
},
{
id: 'B'
parents: ['A']
children: ['C', 'D']
value: 1
},
{
id: 'C'
parents: ['B']
children: []
value: 1
},
{
id: 'D'
parents: ['B']
children: []
value: 1
}
]
A
|
B
/ \
C D
Every node can have an unfixed number of children, and I'm using the parents array to know the tree root (when the parents array is empty).
What I'm trying to do is a recursion function: the sum of the child values is saved in the parent (overwriting the value). If there is only one child, the child value is saved in the parent. So the values accumulate to the root.
Is the tree structure fine? How can I do the function?
Thanks.
Edit:
Expected result:
tree: [
{
id: 'A'
parents: []
children: ['B']
value: 2
},
{
id: 'B'
parents: ['A']
children: ['C', 'D']
value: 2
},
{
id: 'C'
parents: ['B']
children: []
value: 1
},
{
id: 'D'
parents: ['B']
children: []
value: 1
}
]
Another example:
A
/ \
B E
/ \ |
C D F
All node values = 1.
Expected result:
tree: [
{
id: 'A'
parents: []
children: ['B','E']
value: 3
},
{
id: 'B'
parents: ['A']
children: ['C', 'D']
value: 2
},
{
id: 'C'
parents: ['B']
children: []
value: 1
},
{
id: 'D'
parents: ['B']
children: []
value: 1
},
{
id: 'E'
parents: ['A']
children: ['F']
value: 1
},
{
id: 'F'
parents: ['E']
children: []
value: 1
}
]
A value = B value + E value.
B value = C value + D value.
E value = F value.
Please note that the tree structure is not the same as yours, but you can insert other properties too (id for example) if you want.
const tree = {
value: 1,
children: [{
value: 1,
children: [{
value: 1,
children: null
},
{
value: 1,
children: null
}]
}]
};
function sum(node) {
var childSum = 0;
if (!node.children) return node.value;
for (var i = 0; i < node.children.length; i++) {
childSum += sum(node.children[i]);
}
node.value = childSum;
return childSum;
}
sum(tree);
You could store the reference of the root elemnts as well as every item in a hash table for faster access. Then iterate the root elements for getting the wanted sum by using recursion for the children.
This approach just updates the given array.
function update(array) {
var root = [],
references = array.reduce((r, o) => {
if (!o.parents.length) {
root.push(o.id);
}
r[o.id] = o;
return r;
}, Object.create(null));
root.reduce(function sum(s, id) {
var o = references[id];
return s + (o.value = o.children.reduce(sum, 0) || o.value);
}, 0);
return array;
}
var data1 = [{ id: 'A', parents: [], children: ['B'], value: 1 }, { id: 'B', parents: ['A'], children: ['C', 'D'], value: 1 }, { id: 'C', parents: ['B'], children: [], value: 1 }, { id: 'D', parents: ['B'], children: [], value: 1 }],
data2 = [{ id: 'A', parents: [], children: ['B', 'E'], value: 1 }, { id: 'B', parents: ['A'], children: ['C', 'D'], value: 1 }, { id: 'C', parents: ['B'], children: [], value: 1 }, { id: 'D', parents: ['B'], children: [], value: 1 }, { id: 'E', parents: ['A'], children: ['F'], value: 1 }, { id: 'F', parents: ['E'], children: [], value: 1 }]
console.log(update(data1));
console.log(update(data2));
.as-console-wrapper { max-height: 100% !important; top: 0; }
let tree = [
{
id: 'A',
parents: [],
children: ['B'],
value: 1
},
{
id: 'B',
parents: ['A'],
children: ['C', 'D'],
value: 1
},
{
id: 'C',
parents: ['B'],
children: [],
value: 1
},
{
id: 'D',
parents: ['B'],
children: [],
value: 1
}
];
let leafNodes = [];
function ArrayToObject(array, keyField) {
return array.reduce((obj, item) => {
obj[item[keyField]] = item;
if (!item.children.length && !leafNodes.includes(item[keyField])) leafNodes.push(item[keyField]);
return obj;
}, {});
}
let treeObject = ArrayToObject(tree, 'id');
addSumToParentNode(leafNodes);
function addSumToParentNode(childNodes) {
if (childNodes.length) {
let parentNodes = [];
childNodes.map(child => {
if (treeObject[child].parents.length) {
let parent = treeObject[child].parents[0];
if (!parentNodes.includes(parent)) parentNodes.push(parent);
treeObject[parent].value += treeObject[child].value;
}
});
addSumToParentNode(parentNodes);
}
}
console.log(Object.values(treeObject));

Categories