JavaScript flat list to tree WITHOUT parent id? - javascript

This question builds on many similar ones like Construct hierarchy tree from flat list with parent field?
However the twist is that there is no parent id.
e.g.
[
{id: 1, depth: 1, ...},
{id: 2, depth: 2, ...},
{id: 3, depth: 3, ...},
{id: 4, depth: 2, ...},
{id: 5, depth: 1, ...},
{id: 6, depth: 2, ...},
{id: 7, depth: 2, ...},
{id: 8, depth: 1, ...},
{id: 9, depth: 2, ...},
{id: 10, depth: 3, ...},
{id: 11, depth: 3, ...},
]
What is a performant way to construct the following tree?
Note that the children always come after the parent i.e. one can see the tree from the depth value. For example, id 2 is a child of id 1 since its depth is 2 and id 1 has a depth of 1. id 3 is a child of id 2 since id 3 has a depth of 3. id 4 is a child of id 1 not id 3 because id 4 has a depth of 2 (a step up) from id 3's depth of 3
\\tree digram
1
2
3
4
5
6
7
8
9
10
11
Should have values like
[
{id:1, depth:1, children: [
{id: 2, depth: 2, children: [...]},
...
]},
{id:5, depth:1, children: [...]},
{id:6, depth:1, children: [...]},
]

You can use an array for this that has an index for each depth. At every moment, it will represent a path from the (virtual) root to the current node. When dealing with a node, its parent will sit at index depth-1, where it can be inserted in that parent's children property, and the node itself will be placed at index depth:
function createForest(flatdata) {
const path = [{ children: [] }];
for (const obj of flatdata) {
path[obj.depth - 1].children.push(path[obj.depth] = { ...obj, children: [] });
}
return path[0].children;
}
// demo
const flatdata = [{id: 1, depth: 1},{id: 2, depth: 2},{id: 3, depth: 3},{id: 4, depth: 2},{id: 5, depth: 1},{id: 6, depth: 2},{id: 7, depth: 2},{id: 8, depth: 1},{id: 9, depth: 2},{id: 10, depth: 3},{id: 11, depth: 3}];
const roots = createForest(flatdata);
console.log(roots);
Irregular depths
If the depth values do not correspond to the actual depth of the nodes, but leave gaps, then use a "dictionary" (a plain object) to record the mapping of the depth property values with which real depth they correspond with:
function createForest(flatdata) {
const path = [{ children: [] }];
const depthMap = { 0: 0 };
for (const obj of flatdata) {
path[(depthMap[obj.depth] ??= path.length) - 1].children.push(
path[depthMap[obj.depth]] = { ...obj, children: []}
);
}
return path[0].children;
}
// demo
const flatdata = [{id: 1, depth: 10},{id: 2, depth: 20},{id: 3, depth: 30},{id: 4, depth: 20},{id: 5, depth: 10},{id: 6, depth: 20},{id: 7, depth: 20},{id: 8, depth: 10},{id: 9, depth: 20},{id: 10, depth: 30},{id: 11, depth: 30}];
const roots = createForest(flatdata);
console.log(roots);
If however, the only irregularity is that the depth does not always start at 1, but sometimes at 2, it will be more efficient to prefix the input data with a dummy depth-one node, use the first function, and then remove the dummy "root" (with depth 1) from the result.

Go through the array and add each item to the tree as well as to a trail of breadcrumbs. Each next item either goes as a child to the last one or you backtrack through the breadcrumb trail to the correct depth where it needs to be inserted:
const peek = arr =>
arr[arr.length-1];
function toTree(arr) {
const tree = [];
const trail = [];
for (const item of arr) {
while ((peek(trail)?.depth ?? 0) >= item.depth) {
trail.pop();
}
const current = peek(trail)?.children ?? tree;
const treeNode = {...item, children: []};
current.push(treeNode);
trail.push(treeNode);
}
return tree;
}
const array = [
{id: 1, depth: 1, },
{id: 2, depth: 2, },
{id: 3, depth: 3, },
{id: 4, depth: 2, },
{id: 5, depth: 1, },
{id: 6, depth: 2, },
{id: 7, depth: 2, },
{id: 8, depth: 1, },
{id: 9, depth: 2, },
{id: 10, depth: 3 },
{id: 11, depth: 3 },
]
console.log(toTree(array));
This solution clones each item, in order to add the .children property. If no cloning is necessary, item can be directly mutated.

You could take an array of the last inserted objects.
const
data = [{ id: 1, depth: 1 }, { id: 2, depth: 2 }, { id: 3, depth: 3 }, { id: 4, depth: 2 }, { id: 5, depth: 1 }, { id: 6, depth: 2 }, { id: 7, depth: 2 }, { id: 8, depth: 1 }, { id: 9, depth: 2 }, { id: 10, depth: 3 }, { id: 11, depth: 3 }],
result = data.reduce((r, { depth, ...o }) => {
r[depth - 1].push({ ...o, children: r[depth] = [] });
return r;
}, [[]])[0];
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

how to guet maximum value of object with same name and Id

i have an array of objects like below
[
{value: 1, id: 1, name: "x"},
{value: 5, id: 1, name: "x"},
{value: 1, id: 1, name: "y"},
{value: 8, id: 1, name: "y"},
{value: 1, id: 2, name: "x"},
{value: 3, id: 2, name: "x"},
{value: 1, id: 2, name: "y"},
{value: 4, id: 2, name: "y"}
]
i want to guet the object with max value with the same "name" and "id"
and push it in a new array ,
the expected output is like this :
[
{value: 5, id: 1, name: "x"},
{value: 8, id: 1, name: "y"},
{value: 3, id: 2, name: "x"},
{value: 4, id: 2, name: "y"},
]
thank you
You can use reduce method to do this,
const data = [
{value: 1, id: 1, name: "x"},
{value: 5, id: 1, name: "x"},
{value: 1, id: 1, name: "y"},
{value: 8, id: 1, name: "y"},
{value: 1, id: 2, name: "x"},
{value: 3, id: 2, name: "x"},
{value: 1, id: 2, name: "y"},
{value: 4, id: 2, name: "y"}
]
const res = data.reduce((prev, curr) => {
const index = prev.findIndex((item) => item.id === curr.id && item.name === curr.name);
if(index > -1) {
const obj = prev[index];
if(obj.value < curr.value) {
prev[index] = {...obj, value: curr.value};
return prev;
}
}
prev.push(curr);
return prev;
}, []);
console.log(res);
Using Array.prototype.reduce, you can group that array using id_name key pair and store the maximum values as follows.
const input = [
{value: 1, id: 1, name: "x"},
{value: 5, id: 1, name: "x"},
{value: 1, id: 1, name: "y"},
{value: 8, id: 1, name: "y"},
{value: 1, id: 2, name: "x"},
{value: 3, id: 2, name: "x"},
{value: 1, id: 2, name: "y"},
{value: 4, id: 2, name: "y"}
];
const groupBy = input.reduce((acc, cur) => {
const key = `${cur.id}_${cur.name}`;
if (!acc[key]) {
acc[key] = cur;
}
if (acc[key].value < cur.value) {
acc[key].value = cur.value;
}
return acc;
}, {});
const output = Object.values(groupBy);
console.log(output);
Reduce is used to return a new value that is basically accumulator (adds on previous value) from all the items in the array. Here we can use it to group items using specific key. As you wrote you want to have items showing a record with biggest value having same id and name, these values can be taken as a key (lets look at them as composite private keys of this object).
On each iteration, we check if there is already an object with that key added to the list, if it wasn't we add the object we are now on (during iteration) or if it was already added if its value is smaller than the current object we are on. If the value is smaller, we override the object with the current one.
In the end, we use JS Object.values method that strips away the keys and returns only the values of the object.
const list = [
{value: 1, id: 1, name: "x"},
{value: 5, id: 1, name: "x"},
{value: 1, id: 1, name: "y"},
{value: 8, id: 1, name: "y"},
{value: 1, id: 2, name: "x"},
{value: 3, id: 2, name: "x"},
{value: 1, id: 2, name: "y"},
{value: 4, id: 2, name: "y"}
];
const groupedResults = list.reduce((result, currentObject) => {
const currentKey = currentObject.id + currentObject.name;
if (!result[currentKey] || result[currentKey].value < currentObject.value) { /* Here we check if object with certain key was assigned to previously or if it was is the value smaller than of the object that we are currently seeing */
result[currentKey] = Object.assign({}, currentObject) //We need to do copy of the object (it can be also done using object destructuring) in order to have a new object that will not be bound by reference with the original one
};
return result;
}, {});
const requestedList = Object.values(groupedResults);
console.log(requestedList)

JavaScript: How to group two-dimensional array based matching fields on two input arrays? [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 3 years ago.
Improve this question
I have the following arrays a and b as shown:
var a = [
{id: 1, name: "phone"},
{id: 2, name: "nick"}
];
var b = [
{id: 7, parentId: 1, name: "phone_item1"},
{id: 8, parentId: 2, name: "phone_item2"},
{id: 9, parentId: 1, name: "nick_item1"},
{id: 10, parentId: 2, name: "nick_item2"}
];
//You want to filter by A array id
const filterIds = a.map(({ id }) => id);
//My attempt
const c = Object.values(b.reduce((r, c) => {
r[c.parentId] = r[c.parentId] || [];
r[c.parentId].push(c);
return r;
}, {}));
console.log(c)
I am trying to obtain the following two-dimensional array:
[[
{id: 7, parentId: 1, name: "phone_item1"},
{id: 9, parentId: 1, name: "nick_item1"}
],[
{id: 8, parentId: 2, name: "phone_item2"},
{id: 10, parentId: 2, name: "nick_item2"}
]]
The resulting array should be according to the comparison between the id of array a and parentId of array b, so that this style of 2D array results. For some reason my attempt is not working - can you help me?
Not suring if I'm missing something here - assuming I understand your question correctly, a simple implementation would be to map each item of a such that the mapping returns a filtered subset of b on matches between bItem.parentId === aItem.id (where aItem and bItem are items of respective lists being iterated):
var a = [
{id: 1, name: "phone"},
{id: 2, name: "nick"}
];
var b = [
{id: 7, parentId: 1, name: "phone_item1"},
{id: 8, parentId: 2, name: "phone_item2"},
{id: 9, parentId: 1, name: "nick_item1"},
{id: 10, parentId: 2, name: "nick_item2"}
];
/* Map each item in a to a classification of b against a */
const result = a.map(aItem => {
/* For current aItem, return a subset of b, filtered by matches
on bItem.parentId === aItem.id (ie the classification criteria) */
return b.filter(bItem => bItem.parentId === aItem.id)
});
console.log(result)
Type error c.paentId and relation for filterIds which was missing:
var a = [
{id: 1, name: "phone"},
{id: 2, name: "nick"}
];
var b = [
{id: 7, parentId: 1, name: "phone_item1"},
{id: 8, parentId: 2, name: "phone_item2"},
{id: 9, parentId: 1, name: "nick_item1"},
{id: 10, parentId: 2, name: "nick_item2"}
];
const filterIds = a.map(({ id }) => id);
//The problem code
const c = Object.values(b.reduce((r, c) => {
r[c.parentId] = filterIds.includes(c.parentId) && r[c.parentId] || [];
r[c.parentId].push(c);
return r;
}, {}));
console.log(c);

Organizing an object to create keys when needed and/or push different attribute to value pair array

The data structure that I am trying to achieve would look as so :
I would like the list_id to become a key in a object, and hold all the id's of the items that have the matching list id.
var lists = { (list_id)1 : [1, 2, 3]
(list_id)2 : [4, 5, 6]
(list_id)3 : [7, 8, 9]
(list_id)4 : [10, 11, 12] };
this object is created from a json data structure that looks like this:
let json = [{ id: 1, list_id: 1 }, { id: 2, list_id: 1 },
{id: 3, list_id: 1 }, {id: 4, list_id: 2 },
{id: 5, list_id: 2 }, {id: 6, list_id: 2 },
{id: 7, list_id: 3 }, {id: 8, list_id: 3 },
{id: 9, list_id: 3 }, {id: 10, list_id: 4 },
{id: 11, list_id: 4 }, {id: 12, list_id: 4 }]
I can make an object that holds all the list_id's as keys but am getting stumped on pushing the actions_id into the value pair array with the matching list id.
let listAll = {};
json.forEach(function(lista, index, listb) {
listAll[lista.list_id] = [];
if ( listAll[lista.list_id] === lista.list_id){
listAll[lista.list_id].push(lista.id)
} else {
listAll[lista.list_id] = [lista.id];
}
});
My goal is to have and object that contains a key for every list_id currently avaliable from the actions.
Then add every action that contains the matching list_id into a value pair array.
the current output of this code is
{ '1': [ 3 ], '2': [ 6 ], '3': [ 9 ], '4': [ 12 ] }
which does not contain all numbers, each array should contain 3 numbers.
An alternative is using the function reduce to group the objects by a specific key = ['list_id', list_id].join('').
let json = [{ id: 1, list_id: 1 }, { id: 2, list_id: 1 }, {id: 3, list_id: 1 }, {id: 4, list_id: 2 }, {id: 5, list_id: 2 }, {id: 6, list_id: 2 }, {id: 7, list_id: 3 }, {id: 8, list_id: 3 }, {id: 9, list_id: 3 }, {id: 10, list_id: 4 }, {id: 11, list_id: 4 }, {id: 12, list_id: 4 }],
result = json.reduce((a, {id, list_id}) => {
let key = ['list_id', list_id].join(''); // For example: this is creating ['list_id', 1] to list_id1
(a[key] || (a[key] = [])).push(id);
return a;
}, Object.create(null)/*This is only to create an object without prototype -> {}*/);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Why don't you try hasOwnProperty instead?
var listAll = {};
json.forEach(function(list, index) {
if (listAll.hasOwnProperty(list.list_id)) {
listAll[list.list_id].push(list.id);
}else {
listAll[list.list_id] = [list.id];
}
});
console.log(listAll);

Array remapping

We have an array of objects
var items = [
{ id: 1, order_assigned: 2},
{ id: 2, order_assigned: 4},
{ id: 3, order_assigned: 1},
{ id: 4, order_assigned: 5},
{ id: 5, order_assigned: 3}
];
Each have id and some order number.
For example, user changes order number at id 2 to 0. Array now will look like this:
var items = [
{ id: 1, order_assigned: 2},
{ id: 2, order_assigned: 0},
{ id: 3, order_assigned: 1},
{ id: 4, order_assigned: 5},
{ id: 5, order_assigned: 3}
];
Now we need to reindex order_assigned in order to fill gaps in order_assigned order.
i.e. array should look like this
var items = [
{ id: 1, order_assigned: 2},
{ id: 2, order_assigned: 0},
{ id: 3, order_assigned: 1},
{ id: 4, order_assigned: 4},
{ id: 5, order_assigned: 3}
];
Item with id 4 changes order assigned number from 5 to 4, cos there is no item with 4 order assigned number.
Numbering in order_assigned should go in order from 1 to the last established rank. 1, 2, 3, 4. If someone enters 1,2,5,7 to order_assigned it must be converted to 1,2,3,4.
In other words, if there is a gap in order number, all digits after it must be recalculated. No resorting of items order itself needed.
Totally stucked how to do this right way. Please, help!
I think you're asking us to order this based on order_assigned, then close the gaps between the order_assigned.
I'm first sorting the array, then looping through it. I'm then replacing the order_assigned value with the current index.
var items = [
{ id: 1, order_assigned: 2},
{ id: 2, order_assigned: 0},
{ id: 3, order_assigned: 1},
{ id: 4, order_assigned: 5},
{ id: 5, order_assigned: 3}
];
items.sort(function(a, b) {
return a.order_assigned - b.order_assigned;
});
var counter = 0;
for (var index in items) {
if (items[index].order_assigned === 0) {
continue;
} // leave orders that are 0 the same
items[index].order_assigned = ++counter;
}
items.sort(function(a, b) {
return a.id - b.id;
}); // sort again on the ID field
console.log(items);
function sortCloseGap(array) {
return array.slice() // copy
.sort(function(a, b) { return a.order_assigned - b.order_assigned }) // sort
.map(function(item, index) { // assign new order
item.order_assigned = index + 1;
return item;
});
}
I hope you don't mind the mutability.

Traverse an object tree and render a menu tree based on object property condition

Hello I'm trying to create an html menu from a given object like the following and I was wondering if there is a library that can help me traverse the object, instead of reinventing the wheel.
var root = {
category1: {
depth: 0,
categories: {
category1: {
depth: 1
},
category2: {
depth: 1,
products: [1, 2, 3, 4, 5]
},
category3: {
depth: 1,
categories: {
category1: {
depth: 2,
products: [1, 2, 3, 4]
},
category2: {
depth: 2,
products: [1, 2, 3, 4, 5, 6, 7]
},
category3: {
depth: 2
}
}
}
}
},
category2: {
depth: 0,
category1: {
depth: 1,
},
category2: {
depth: 1,
products:[1,2,3,4,5]
}
},
category3: {
depth: 0,
category1: {
depth: 1,
},
category2: {
depth: 1,
categories: {
category1: {
depth: 2,
products: [1, 2, 3, 4]
},
category2: {
depth: 2,
products: [1, 2, 3, 4, 5, 6, 7]
},
category3: {
depth: 2
}
}
}
},
depth: 0,
category4: {
category1: {
depth: 1,
products:[1,2,3,4,5]
},
category2: {
depth: 1,
categories: {
category1: {
depth: 2,
products: [1, 2, 3, 4]
},
category2: {
depth: 2,
products: [1, 2]
},
category3: {
depth: 2
}
}
},
category3: {
depth: 1,
}
}
};
I'm interested in creating menu paths - nodes which their leaf categories have products all other must be discarded and not rendered. Also products can be found in level 1 and 2 categories. And each category has a depth indicator property indicating the level of the category.
For example if a root category has no leaf level category with products it will not be rendered, or a depth 1 category with no leafs with products will also must not be rendered.

Categories

Resources