Map Array inside array using lodash - javascript

I'm trying to manipulate this sample array of objects.
var data = [
{ id: 'A', name: 'Test1', parentId: null },
{ id: 'B', name: 'Test2', parentId: 'A'},
{ id: 'C', name: 'Test3', parentId: 'A'},
{ id: 'D', name: 'Test4', parentId: null },
{ id: 'E', name: 'Test5', parentId: 'D'},
{ id: 'F', name: 'Test6', parentId: 'D'},
];
What I need to do is to map array to array like that
var data = [
{
id: 'A',
name: 'Test1',
parentId: null,
children:[
{ id: 'B', name: 'Test2', parentId: 'A'},
{ id: 'C', name: 'Test3', parentId: 'A'}
],
},
{
id: 'D',
name: 'Test4',
parentId: null,
children:[
{ id: 'E', name: 'Test5', parentId: 'D'},
{ id: 'F', name: 'Test6', parentId: 'D'}
],
},
];
What is the simplest way to do that, using lodash? Please help me.

_.groupBy is more effective in such scenarios. You need to group the elements by parentId, so that you can easily assign their children later.
var data = [
{ id: 'A', name: 'Test1', parentId: null },
{ id: 'B', name: 'Test2', parentId: 'A'},
{ id: 'C', name: 'Test3', parentId: 'A'},
{ id: 'D', name: 'Test4', parentId: null },
{ id: 'E', name: 'Test5', parentId: 'D'},
{ id: 'F', name: 'Test6', parentId: 'D'},
];
var map = _.groupBy(data, 'parentId');
map[null].forEach(item => item.children = map[item.id]);
console.log(map[null]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

You can do this using reduce method and create nested structure for any depth level.
var data = [{ id: 'A', name: 'Test1', parentId: null },{ id: 'B', name: 'Test2', parentId: 'A'},{ id: 'C', name: 'Test3', parentId: 'A'},{ id: 'D', name: 'Test4', parentId: null },{ id: 'E', name: 'Test5', parentId: 'D'},{ id: 'F', name: 'Test6', parentId: 'D'}];
function tree(data, parentId = null) {
return _.reduce(data, (r, e) => {
if (parentId == e.parentId) {
const o = _.clone(e);
const children = tree(data, e.id);
if (children.length) o.children = children;
r.push(o)
}
return r;
}, [])
}
const result = tree(data);
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.js"></script>

The usage of _.groupBy is the closest to your needs, just select parentId as previously said. If you need it to be recursive, you could try something like this:
var data = [
{ id: 'A', name: 'Test1', parentId: null },
{ id: 'B', name: 'Test2', parentId: 'A'},
{ id: 'C', name: 'Test3', parentId: 'A'},
{ id: 'D', name: 'Test4', parentId: null },
{ id: 'E', name: 'Test5', parentId: 'B'},
{ id: 'F', name: 'Test6', parentId: 'D'},
];
function assignChildrens(grouped, parentId) {
if (!grouped[parentId]) return [];
return _.map(grouped[parentId], v => {
v.childrens = assignChildrens(grouped, v.id);
return v;
});
}
var finalData = assignChildrens(_.groupBy(data, 'parentId'), null);
That way, you could have nested elements and still works.

You could take a single loop approach which takes the relation between children and parent and parents and children into a hash table and resturns only the children of the given root nodes.
var data = [{ id: 'A', name: 'Test1', parentId: null }, { id: 'B', name: 'Test2', parentId: 'A'}, { id: 'C', name: 'Test3', parentId: 'A'}, { id: 'D', name: 'Test4', parentId: null }, { id: 'E', name: 'Test5', parentId: 'D'}, { id: 'F', name: 'Test6', parentId: 'D'}],
tree = function(data, root) {
var t = {};
data.forEach(o => {
Object.assign(t[o.id] = t[o.id] || {}, o);
t[o.parentId] = t[o.parentId] || { id: o.parentId };
t[o.parentId].children = t[o.parentId].children || [];
t[o.parentId].children.push(t[o.id]);
});
return t[null].children;
}(data, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

How to filter array of object and filter out values based on another array? Filtering should happen based on keys not values

I have an array of object which must be filtered based on another array, the keys are listed in the allowed array, pls help tired using object.entries and reduce but didn't work
const filter = _.filter;
const data = [{
id: 1,
row: [{
id: 'a',
name: 'ab',
code: 'sdf',
version: 1
},
{
id: 'b',
name: 'bc',
code: 'def',
version: 3
},
{
id: 'c',
name: 'cd',
code: 'afd',
version: 2
},
]
},
{
id: 2,
row: [{
id: 'd',
name: 'ef',
code: 'sdf',
version: 1
},
{
id: 'e',
name: 'gh',
code: 'def',
version: 3
},
{
id: 'f',
name: 'ij',
code: 'afd',
version: 2
},
]
},
{
id: 3,
row: [{
id: 'g',
name: 'kl',
code: 'asd',
version: 2
},
{
id: 'h',
name: 'mn',
code: 'faf',
version: 3
},
{
id: 'i',
name: 'op',
code: 'dfs',
version: 1
},
]
}
]
const allowed = ['id', 'name']
let result = [{
id: 1,
row: [{
id: 'a',
name: 'ab'
},
{
id: 'b',
name: 'bc'
},
{
id: 'c',
name: 'cd'
},
]
},
{
id: 2,
row: [{
id: 'd',
name: 'ef'
},
{
id: 'e',
name: 'gh'
},
{
id: 'f',
name: 'ij'
},
]
},
{
id: 3,
row: [{
id: 'g',
name: 'kl'
},
{
id: 'h',
name: 'mn'
},
{
id: 'i',
name: 'op'
},
]
}
]
result = data.filter(el => el.row.filter(elm => Object.fromEntries(allowed.map(k => [k, elm[k]]))));
console.log(result);
You can create a new array with Array.map.
Logic
Map through the array.
Just spread operator to seperate out row key and rest of keys.
return an object with rest of keys and row key as with the Object.fromEntries
const data = [{
id: 1,
row: [
{ id: 'a', name: 'ab', code: 'sdf', version: 1 },
{ id: 'b', name: 'bc', code: 'def', version: 3 },
{ id: 'c', name: 'cd', code: 'afd', version: 2 },
]
},
{
id: 2,
row: [
{ id: 'd', name: 'ef', code: 'sdf', version: 1 },
{ id: 'e', name: 'gh', code: 'def', version: 3 },
{ id: 'f', name: 'ij', code: 'afd', version: 2 },
]
},
{
id: 3,
row: [
{ id: 'g', name: 'kl', code: 'asd', version: 2 },
{ name: 'mn', code: 'faf', version: 3 },
{ id: 'i', name: 'op', code: 'dfs', version: 1 },
]
}
]
const allowed = ['id', 'name'];
const result = data.map(({ row, ...rest }) => {
return {
...rest,
row: row.map(elm => Object.fromEntries(allowed.map(k => [k, elm[k]])))
}
});
console.log(result);
Long way but it works:
const data = [
{id: 1, row: [
{id: 'a', name: 'ab', code: 'sdf', version: 1},
{id: 'b', name: 'bc', code: 'def', version: 3},
{id: 'c', name: 'cd', code: 'afd', version: 2},
]
},
{id: 2, row: [
{id: 'd', name: 'ef', code: 'sdf', version: 1},
{id: 'e', name: 'gh', code: 'def', version: 3},
{id: 'f', name: 'ij', code: 'afd', version: 2},
]
},
{id: 3, row: [
{id: 'g', name: 'kl', code: 'asd', version: 2},
{id: 'h', name: 'mn', code: 'faf', version: 3},
{id: 'i', name: 'op', code: 'dfs', version: 1},
]
}
];
const allowed = ['id', 'name'];
let res = [];
data.forEach((el) => {
let obj = {};
obj.id = el.id;
obj["row"] = [];
let row = buildArray(el.row);
obj["row"].push(row);
res.push(obj);
})
function buildArray(row) {
r = {};
allowed.forEach((k) => {
r[k] = row[0][k];
})
return r;
}
console.log(res)

How to get a "minimalist" array of objects from an array of objects with a pletora of attributes

I have an array, like this:
let x = [
{id: 1, name: 'A', age: 34.. lots of other properties},
{id: 2, name: 'B', age: 17.. },
{id: 3, name: 'C', age: 54.. }
]
How can I get this output from it:
let output = [
{id: 1, name: 'A'},
{id: 2, name: 'B'},
{id: 3, name: 'C'}
]
I mean, I can iterate throut it, and push new objects into a new array. But I was thinking if there's a better way to do it..
More generally, for any set of attributes, the idea is called “pick” and "pluck" in lodash and underscore...
let x = [
{id: 1, name: 'A', age: 34 },
{id: 2, name: 'B', age: 17 },
{id: 3, name: 'C', age: 54 }
]
function pick(object, ...attributes) {
const filtered = Object.entries(object).filter(([k, v]) => attributes.includes(k))
return Object.fromEntries(filtered);
}
function pluck(array, ...attributes) {
return array.map(el => pick(el, ...attributes))
}
console.log(pluck(x, 'id', 'name'))
You can use .map
let x = [
{id: 1, name: 'A', age: 34.,},
{id: 2, name: 'B', age: 17., },
{id: 3, name: 'C', age: 54., }
];
console.log(x.map(({id, name}) => ({id, name})));

How to merge trees of nodes into one when elements are duplicated

How could I solve a case where I have to merge trees with repeated values? For example something like that:
const firstTree = {
id: 1,
name: 'name1',
otherProperty: 'property1',
children: [
{
id: '1a',
name: 'name1a',
otherProperty: 'property1a',
children: []
},
{
id: '1b',
name: 'name1b',
otherProperty: 'property1b',
children: [
{
id: '1ba',
name: 'name1ba',
otherProperty: 'property1ba',
children: []
},
{
id: '1bb',
name: 'name1bb',
otherProperty: 'property1bb',
children: []
}
]
}
]
};
const secondTree = {
id: 1,
name: 'name1',
otherProperty: 'property1',
children: [
{
id: '1b',
name: 'name1b',
otherProperty: 'property1b',
children: [
{
id: '1ba',
name: 'name1ba',
otherProperty: 'property1ba',
children: []
},
{
id: '2ba',
name: 'name2ba',
otherProperty: 'property2ba',
children: []
}
]
}
]
};
const thirdTree = {
id: '3',
name: 'name3',
otherProperty: 'property3',
children: [
{
id: '3a',
name: 'name3a',
otherProperty: 'property3a',
children: []
},
{
id: '3b',
name: 'name3b',
otherProperty: 'property3b',
children: []
}
]
};
const entryArray = [firstTree, secondTree, thirdTree];
And I want to have as a result merged the first tree with the second and additionaly the third tree where there were no common elements :
const mergedFirstAndSecond = {
id: 1,
name: 'name1',
otherProperty: 'property1',
children: [
{
id: '1a',
name: 'name1a',
otherProperty: 'property1a',
children: []
},
{
id: '1b',
name: 'name1b',
otherProperty: 'property1b',
children: [
{
id: '1ba',
name: 'name1ba',
otherProperty: 'property1ba',
children: []
},
{
id: '1bb',
name: 'name1bb',
otherProperty: 'property1bb',
children: []
},
{
id: '2ba',
name: 'name2ba',
otherProperty: 'property2ba',
children: []
}
]
}
]
};
const result = [mergedFirstAndSecond, thirdTree];
I mean a solution that would also work if the duplicate elements also occurred in three different trees, not just two. I will be very grateful for any suggestions.
You could first create the tree as a nested map structure (where each children property is a Map instance), and merge all trees in that structure. This will allow optimised lookup to see where an entry needs to be merged into.
Once you have that, replace all these children properties back to arrays.
function mergeTrees(...trees) {
function fillMap(src, map) {
let dst = map.get(src.id);
if (!dst) map.set(src.id, dst = { ...src, children: new Map });
for (let child of (src.children ?? [])) fillMap(child, dst.children);
}
// Merge into nested Map structure:
let mergedTree = new Map;
for (let tree of trees) fillMap(tree, mergedTree);
// Convert each map to array:
const toArrays = map => Array.from(map.values(), node =>
Object.assign(node, { children: toArrays(node.children) })
);
return toArrays(mergedTree);
}
// Demo
const firstTree = {id: 1,name: 'name1',otherProperty: 'property1',children: [{id: '1a',name: 'name1a',otherProperty: 'property1a',children: []}, {id: '1b',name: 'name1b',otherProperty: 'property1b',children: [{id: '1ba',name: 'name1ba',otherProperty: 'property1ba',children: []}, {id: '1bb',name: 'name1bb',otherProperty: 'property1bb',children: []}]}]};
const secondTree = {id: 1,name: 'name1',otherProperty: 'property1',children: [{id: '1b',name: 'name1b',otherProperty: 'property1b',children: [{id: '1ba',name: 'name1ba',otherProperty: 'property1ba',children: []}, {id: '2ba',name: 'name2ba',otherProperty: 'property2ba',children: []}]}]};
const thirdTree = {id: '3',name: 'name3',otherProperty: 'property3',children: [{id: '3a',name: 'name3a',otherProperty: 'property3a',children: []}, {id: '3b',name: 'name3b',otherProperty: 'property3b',children: []}]};
const entryArray = mergeTrees(firstTree, secondTree, thirdTree);
console.log(entryArray);
You can use recursion to merge the trees on their ids:
var firstTree = {'id': 1, 'name': 'name1', 'otherProperty': 'property1', 'children': [{'id': '1a', 'name': 'name1a', 'otherProperty': 'property1a', 'children': []}, {'id': '1b', 'name': 'name1b', 'otherProperty': 'property1b', 'children': [{'id': '1ba', 'name': 'name1ba', 'otherProperty': 'property1ba', 'children': []}, {'id': '1bb', 'name': 'name1bb', 'otherProperty': 'property1bb', 'children': []}]}]};
var secondTree = {'id': 1, 'name': 'name1', 'otherProperty': 'property1', 'children': [{'id': '1b', 'name': 'name1b', 'otherProperty': 'property1b', 'children': [{'id': '1ba', 'name': 'name1ba', 'otherProperty': 'property1ba', 'children': []}, {'id': '2ba', 'name': 'name2ba', 'otherProperty': 'property2ba', 'children': []}]}]};
var thirdTree = {'id': '3', 'name': 'name3', 'otherProperty': 'property3', 'children': [{'id': '3a', 'name': 'name3a', 'otherProperty': 'property3a', 'children': []}, {'id': '3b', 'name': 'name3b', 'otherProperty': 'property3b', 'children': []}]};
var entryArray = [firstTree, secondTree, thirdTree];
function merge_trees(trees){
var merger = {};
//find matches based on id
for (var t of trees){
for (var i of t){
if (!(i['id'] in merger)){
merger[i['id']] = Object.fromEntries(Object.keys(i).map(x => [x, (new Set([i[x]]))]));
}
for (var k of Object.keys(i)){
merger[i['id']][k] = new Set([...(k in merger[i['id']] ? merger[i['id']][k] : []), i[k]]);
}
}
}
var new_result = [];
//iterate over merges
for (var i of Object.keys(merger)){
var result = {}
for (var k of Object.keys(merger[i])){
//choose whether or not to merge again based on the size of the merged children
if (k === 'children'){
result[k] = merger[i][k].size > 1 ? merge_trees(merger[i][k]) : [...merger[i][k]].filter(x => x.length > 1)
}
else{
result[k] = merger[i][k].size === 1 ? [...merger[i][k]][0] : [...merger[i][k]]
}
}
new_result.push(result)
}
return new_result;
}
console.log(merge_trees(entryArray.map(x => [x])))

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

Array transformation tree structure

I have an array that needs to be converted to a tree structure, and I've searched for some scenarios, such as the following code, but once the amount of data is too large, the time required to generate this tree structure seems too long, ask if there can be a better solution or optimization!
var data = [{ id: 1, name: 'A', parentId: 0 },{ id: 2, name: 'B', parentId: 1 },{ id: 3, name: 'C', parentId: 2 },{ id: 4, name: 'D', parentId: 3 },{ id: 5, name: 'E', parentId: 4 },{ id: 6, name: 'F', parentId: 5 },{ id: 7, name: 'G', parentId: 6 },{ id: 8, name: 'H', parentId: 7 },{ id: 9, name: 'Z', parentId: 8 }];
var toTree = function(data) {
var map = {};
data.forEach(function(item) {
map[item.id] = item;
});
var val = [];
data.forEach(function(item) {
var parent = map[item.parentId];
if (parent) {
(parent.children || (parent.children = [])).push(item);
} else {
val.push(item);
}
});
return val;
}
console.log(toTree(data))
You could use a single loop approach where id and parentId is used for building a tree in the same loop.
At the end return the root node's children.
var data = [{ id: 1, name: 'A', parentId: 0 }, { id: 2, name: 'B', parentId: 1 }, { id: 3, name: 'C', parentId: 2 }, { id: 4, name: 'D', parentId: 3 }, { id: 5, name: 'E', parentId: 4 }, { id: 6, name: 'F', parentId: 5 }, { id: 7, name: 'G', parentId: 6 }, { id: 8, name: 'H', parentId: 7 }, { id: 9, name: 'Z', parentId: 8 }],
tree = function (data, root) {
var o = {};
data.forEach(function (a) {
a.children = o[a.id] && o[a.id].children;
o[a.id] = a;
o[a.parentId] = o[a.parentId] || {};
o[a.parentId].children = o[a.parentId].children || [];
o[a.parentId].children.push(a);
});
return o[root].children;
}(data, 0);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One approach is to change your data in the first place, so that it does not come from an array but a tree structure map and data map:
var treeStructure = {
'ROOT': ['childId1', 'childId2'],
'childId1': ['childId3']
}
var data = {
'childId1': {
'property1':1
},
'childId2': {
'property1':2
},
'childId3': {
'property1': 3
}
}
This is so that the relationship of parent-child will be very clear and it will be very easy to find each item.

Categories