Unflatten array to Tree without parent id but with level - javascript

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

Related

JavaScript: Get non-repeating items from 2d array of objects

Compare 2d array of objects and convert it into single array of objects by getting the unique items (non-repeating in terms of id) using Javascript.
Sample input:
const data = [
[ { name: 'x', id: 1 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'y', id: 12 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'z', id: 22 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ]
];
Expected output:
const out = [
{ name: 'a', id: 1 },
{ name: 'b', id: 12 },
{ name: 'b', id: 22 }
]
Using Array#flat, convert the 2d array into one
Using Array#reduce, iterate over the latter while updating a Map where the id is the key and the count is the value
Using Map#entries, get the list of id-count pairs from the Map. Then, using Array#filter, get the ids whose count is 1. Finally, using Array#map, get the list of resulting ids
Using Array#filter, iterate over the flattened array of objects and return the elements whose ids belong to the non-repeating ids list
const data = [
[ { name: 'x', id: 1 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'y', id: 12 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'z', id: 22 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ]
];
const arr = data.flat();
const idCountMap = arr.reduce((map, { id }) => map.set(id, (map.get(id) ?? 0) + 1), new Map);
const nonRepeatingIds =
[...idCountMap.entries()]
.filter(([ id, count ]) => count === 1)
.map(([ id ]) => id);
const nonRepeatingItems = arr.filter(({ id }) => nonRepeatingIds.includes(id));
console.log(nonRepeatingItems);
You can do this by converting the use case in two parts.
Part 1 : Concat the array of objects to a new array
var array = [];
data.forEach(val => {
array = [...array, ...val];
});
Part 2 : Find the unique values in the new array
const result = [];
const map = new Map();
for (const item of array) {
if(!map.has(item.id)){
map.set(item.id, true); // set any value to Map
result.push({
id: item.id,
name: item.name
});
}
}
console.log(result)
you have to loop the array of array and check if you already have added the id or not. In this way you can make the id unique in the result
const data= [
[{name:"x" , id:1},{name:"a" , id:13},{name:"a" , id:14},{name:"a" , id:15},{name:"a" , id:16},],
[{name:"x" , id:1},{name:"a" , id:13},{name:"a" , id:14},{name:"a" , id:15},{name:"a" , id:16},],
[{name:"x" , id:1},{name:"a" , id:13},{name:"a" , id:14},{name:"a" , id:15},{name:"a" , id:16},]
]
//loop for each array and filter
var addedId = [];
//init result
var out = [];
//loop array
data.forEach(function(arr){
//loop element in array
arr.forEach(function(obj){
// check if the id has been already added to the result
if(!addedId.includes(obj.id)){
addedId.push (obj.id);
out.push(obj);
}
})
})

Re-arrange a flat array with level info into n-ary tree with childrens

Given an array in the format of levels and their immediate children store in a consecutive array return a n-ary tree
Given Input format :
[{'name':'a', 'level': -1},
{'name':'b', 'level': 0},
{'name':'c', 'level': 1},
{'name':'d', 'level': 2},
{'name':'e', 'level': 0},
{'name':'f', 'level': 1},
{'name':'g', 'level': 0}
];
Expected Output should be in the below format:
[
{
name:"a",
level:-1,
children: [
{
name:"b",
level:0,
children: [
{
name:"c",
level:1,
children: [
{
name:"d",
level:2,
children: [ ]
}
]
}
]
}
],
},
{
name:"e",
level:1,
children: [
{
name:"f",
level:2,
children: [ ]
}
]
},
{
name:"g",
level:2,
children: [ ]
}
]
The recursive solution I tried to implement which failed
Above code returns
function treeTraversal(arr, index) {
if (arr === null || arr.length === 0 || index === arr.length) {
return;
}
let result = [];
let children = [];
if (arr[index - 1].level + 1 === arr[index].level) {
children.push(arr[index]);
console.log(children);
treeTraversal(arr, index + 1);
}
arr[index - 1].children = children;
result.push(arr[index - 1]);
return result;
}
const arr = [{
'name': 'a',
'level': -1
},
{
'name': 'b',
'level': 0
},
{
'name': 'c',
'level': 1
},
{
'name': 'd',
'level': 2
},
{
'name': 'e',
'level': 0
},
{
'name': 'f',
'level': 1
},
{
'name': 'g',
'level': 0
}
];
console.log(treeTraversal(arr, 1));
unexpected results. Please let me know what's wrong with the above code
You could take a helper array for the levels and assign the object to the latest array of the level plus one.
var data = [{ name: 'a', level: -1 }, { name: 'b', level: 0 }, { name: 'c', level: 1 }, { name: 'd', level: 2 }, { name: 'e', level: 0 }, { name: 'f', level: 1 }, { name: 'g', level: 0 }],
tree = [],
levels = [tree];
data.forEach(o => levels[o.level + 1].push({ ...o, children: levels[o.level + 2] = [] }));
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Group consecutive similar array of elements

Here is my simple array of objects
const array = [
{ name: 'a', val: '1234' },
{ name: 'b', val: '5678' },
{ name: 'c', val: '91011' },
{ name: 'c', val: '123536' },
{ name: 'e', val: '5248478' },
{ name: 'c', val: '5455' },
{ name: 'a', val: '548566' },
{ name: 'a', val: '54555' }
]
I need to group consecutive name elements and push the corresponding val. So the expected output should be
const array = [
{ name: 'a', vals: '1234' },
{ name: 'b', vals: '5678' },
{ name: 'c', vals: ['91011', '123536'] },
{ name: 'e', vals: '5248478' },
{ name: 'c', vals: '5455' },
{ name: 'a', vals: ['548566', '54555'] }
]
I tried it But could not get over it. Please help
const output = []
const result = array.reduce((a, c) => {
if (a.name === c.name) {
output.push(a);
}
}, []);
You were actually quite close:
const output = [];
array.reduce((a, c) => {
if (a.name === c.name) { // current element equals previous element, lets merge
a.values.push(c.val);
} else output.push(a = { name: c.name, values: [c.val] ); // otherwise add new entry
return a; // the current element is the next previous
} , {}); // start with an empty a, so that c always gets pushed
Note that it makes little sense to store numbers as string though.
You can reduce the array like this. Compare the current name with previous item's name. If they are not the same, add a new item to the accumulator. If they are the same, then use concat the merge val with the last item in accumulator. concat is used because vals could either be a string or an array.
const array = [
{ name: 'a', val: '1234' },
{ name: 'b', val: '5678' },
{ name: 'c', val: '91011' },
{ name: 'c', val: '123536' },
{ name: 'e', val: '5248478' },
{ name: 'c', val: '5455' },
{ name: 'a', val: '548566' },
{ name: 'a', val: '54555' }
]
const merged = array.reduce((acc, { name, val }, i, arr) => {
// check if name is same as the previous name
if (arr[i - 1] && arr[i - 1].name === name) {
const prev = acc[acc.length - 1]; // last item in the accumulator
prev.vals = [].concat(prev.vals, val)
} else
acc.push({ name, vals: val })
return acc
}, [])
console.log(merged)

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.

Recursive Tree insertion in 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.

Categories

Resources