Related
here is an array presented
id: 0,
parent: 'p1',
children: [
{
id: 1,
parent: 'p2',
children: [
{
id: 3,
parent: 'p4',
children: []
},
]
},
{
id: 2,
parent: 'p3',
children: [
{
id: 4,
parent: 'p5',
children: []
},
]
}
]
}
I am trying to search an element based upon the id value, and remove it from the array and
replace it with empty array
for example, if I pass
let childTobeReplaced = childrenCollector(4, treeData.children);
the result must be
{
id: 0,
parent: 'p1',
children: [
{
id: 1,
parent: 'p2',
children: [
{
id: 3,
parent: 'p4',
children: []
},
]
},
{
id: 2,
parent: 'p3',
children: []
}
]
}
and childTobeReplaced must be equal to
{
id: 4,
parent: 'p5',
children: []
}
The solution I implemented is as under
function childrenCollector(sourceId, nestedarray) {
for (let index = 0; index < nestedarray.length; index++) {
console.log(nestedarray[index].id)
if (nestedarray[index].id === sourceId) {
let childArray = nestedarray[index];
nestedarray[index] = []
return childArray;
}
if (nestedarray[index].children.length > 0) {
return childrenCollector(sourceId, nestedarray[index].children);
}else{
}
}
}
In this I am able to iterate through id 1 and id 3 but not able to find rest of the elements
. Can someone present me with some guidance?
Your function will return in the first iteration of the loop when the object has children, which means that the rest of the array is not inspected. That's not what you want. Only exit the loop when you are positive that the element was found by the recursive call.
Secondly, nestedarray[index] = [] is not removing the index from nestedarray. It merely replaces the reference to an object at that index with one to an empty array.
Here is a possible implementation:
function childrenCollector(sourceId, nestedArray) {
const i = nestedArray.findIndex(({id}) => id === sourceId);
let found;
if (i > -1) [found] = nestedArray.splice(i, 1)
else nestedArray.some(({children}) =>
found = childrenCollector(sourceId, children)
);
return found;
}
// Example data from the question
const treeData = {id: 0,parent: 'p1',children: [{id: 1,parent: 'p2',children: [{id: 3,parent: 'p4',children: []},]},{id: 2,parent: 'p3',children: [{id: 4,parent: 'p5',children: []},]}]};
const childTobeReplaced = childrenCollector(4, treeData.children);
console.log(childTobeReplaced);
console.log(treeData);
Explanation:
With findIndex the code tries to find the id in the given array. The callback function uses destructuring to let id be the property of the iterated object. When the callback function returns true, the findIndex iteration stops, and the corresponding index is assigned to i. If there was no match, i will be set to -1.
If there was a match, splice will extract that element from the array at that index, and will return an array of removed elements. Since there is only one removed element, that array will have just one element. This element is assigned to found, again using destructuring assignment.
If there was no match, then the array is iterated again, but now to make the recursive calls. For this iteration some is used, which also will stop the iteration as soon as there is success. In each iteration, found is set to the result from the recursive call. As soon as this is an object (indicating something was found), the loop exits.
In either case the found object (if any) is returned, or else undefined.
Here's a simple immutable solution:
let remove = (node, id) => ({
...node,
children: node.children
.filter(child => child.id !== id)
.map(child => remove(child, id))
})
Given a node, it removes children with the given id and then applies the same procedure to each remaining child.
I have an array of objects representing something similar to the system's hard-drive folder structure. Data is represented in a nested array of objects. Each object is a folder containing some files and folders. I know exactly the sum of the size of files directly placed in each node. But I don't know how much space a node has taken containing its child nodes.
Here is an example of the data:
[{
id: 1,
name: 'root',
filesSize: 123456,
children: [{
id: 2,
name: 'child 1',
filesSize: 789654,
},
{
id: 3,
name: 'child 2',
filesSize: 321123,
children: [{
id: 4,
name: 'child 3 - 1',
filesSize: 88888,
},
{
id: 5,
name: 'child 3 - 2',
filesSize: 99999,
children: [{
id: 99999,
name: 'child m - n',
filesSize: ...,
},
.
.
.
}]
}]
}]
I tried to use Array.reduce, but it doesn't help me because it only iterates on direct children of object - not n level of the nested array. Something like this:
const parentSize = typeof parent['total'] !== 'undefined' ? parent['total'] : parent.filesSize;
parent['total'] = children.reduce((sum, child) => {
return sum + (typeof child.total !== 'undefined' ? child.filesSize : child.total);
}, parentSize);
What am I missing?
Using reduce is fine, but you need:
recursion, so that this reduce is also called on the children when needed.
to always assign to the total. Checking that it already exists is not useful, as it will not. And if it does, it is risky to rely on it, as you don't know whether the tree had been modified after that property was added.
let tree = [{id: 1,name: 'root',filesSize: 123456,children: [{id: 2,name: 'child 1',filesSize: 789654,}, {id: 3,name: 'child 2',filesSize: 321123,children: [{id: 4,name: 'child 3 - 1',filesSize: 88888,}, {id: 5,name: 'child 3 - 2',filesSize: 99999,children: [{id: 99999,name: 'child m - n',filesSize: 1234}]}]}]}];
let totalSize = tree.reduce(function recur(sum, child) {
return sum + (child.total = (child.children ?? []).reduce(recur, child.filesSize));
}, 0);
// The returned size can be interesting when the top level has
// multiple entries (which is not the case in your example):
console.log(totalSize);
console.log(tree);
Just a note:
Assuming your size unit is bytes:
1 petabyte is 1_000_000_000_000_000 bytes (or 1e3 ** 5): so your sum (which is a JS number) can safely hold Number.MAX_SAFE_INTEGER / (1e3 ** 5), which is about 9. If you expect that your size will approach 9 petabytes, you should use a BigInt type to store the sum instead of a number.
The other answers demonstrate recursive approaches, which are terse and elegant, but will fail if your hierarchy is too numerous (stack overflow!).
Here's an iterative alternative:
TS Playground
function getTotal (containerNode) {
let total = 0;
const stack = [containerNode];
while (stack.length > 0) {
const node = stack.pop();
// Already available, skip children
if (typeof node.total === 'number') {
total += node.total;
continue;
}
total += node.filesSize;
if (!node.children?.length) continue;
for (const child of node.children) stack.push(child);
}
return total;
}
// Example usage:
const nodes = [
{
id: 1,
name: 'root',
filesSize: 123456,
children: [
{
id: 2,
name: 'child 1',
filesSize: 789654,
},
{
id: 3,
name: 'child 2',
filesSize: 321123,
total: 321123 + 88888 + 99999 + 34523,
children: [
{
id: 4,
name: 'child 3 - 1',
filesSize: 88888,
},
{
id: 5,
name: 'child 3 - 2',
filesSize: 99999,
children: [
{
id: 99999,
name: 'child m - n',
filesSize: 34523,
},
// ...
],
},
],
},
],
},
];
function test (containerNode, expectedTotal) {
const actual = getTotal(containerNode);
return `${containerNode.id}: ${actual === expectedTotal ? 'pass' : 'fail'}`;
}
const results = [
test(
nodes[0].children[1].children[1].children[0],
34523,
),
test(
nodes[0].children[1].children[1],
99999 + 34523,
),
test(
nodes[0].children[1].children[0],
88888,
),
test(
nodes[0].children[1],
321123 + 88888 + 99999 + 34523,
),
test(
nodes[0].children[0],
789654,
),
test(
nodes[0],
123456 + 789654 + 321123 + 88888 + 99999 + 34523,
),
];
for (const result of results) console.log(result);
I took the liberty of doubling the objects from top to bottom so it splits at the root four levels deep.
/**#function
* #Name totalFileSize
* #Description - This function accepts array of objects and any nested array of
* objects as well. It will extract a number value of a given key
* and recursively search all sub-arrays as well. It will return the sum of all
* extracted values.
* #param {array<object>} objArr - An array of objects
* #param {string} prop - A key/property with a number value
* #returns {number} - A sum of all extracted number values
*/
Pass the array of objects and the property you want to get the total sum of.
// data↘️ ↙️"filesSize"
const totalFileSize = (objArr, prop) =>
Next, run each object through .map() and then convert each object into an array of pairs by using Object.entries(). An array of pairs is a 2D array in which the sub-arrays consist of two elements:
[[key, value], [key, value],...]
objArr.map(obj => Object.entries(obj)
// [{A: 1, B: 'z'}, {...},...] => [["A", 1], ["B", "z"], [...],...]
Now we have a 2D array witch is easier to work with using Array methods. The logical choice of methods is .reduce() since we need a single result. The second parameter represents the element of the current iteration, note it is destructured into an array (specifically a key/value pair). In this form we can easily construct more granular expressions.
// ↙️On each iteration, this value accumulates when a match is made
.reduce((sum, [key, val]) =>
// property↗️ ↖️value
The destructured values allow us to write a more terse and direct way. The heart of this function is a if/else if/else statement as a ternary conditional.
/* if the current key matches prop ("filesSize"), then add sum and the current value
(val).*/
key == prop ? sum + parseInt(val) :
/* Note: the value needed to be converted to a number because the + operator coerced
it into a String.*/
Now onto the 2nd (or else if) of the ternary operator. I noticed there's a running total property in the other answers, so here's where to get it: obj.total = sum + parseInt(totalFileSize(val, prop)). It's the sum and the return value of each recursive call. I don't think the total should be calculated for each object (all you get is a duplicate value of filesSize).
/* else if val is an array recursively invoke totalFileSize() and pass val and
prop in...*/
Array.isArray(val) ? obj.total = sum + parseInt(totalFileSize(val, prop)) :
// ...then convert it's return into a number and add it to sum
else add 0 to sum. Any non-matching values are no longer a worry.
0, 0))
// The last zero is the initial value of `.reduce()`
Last thing to do is cleanup the return value. The data was doubled so I can demonstrate the functions ability to handle multiple branches. At this point all numbers from "filesSize" are now two totals in an array. This last step adds all branches together.
.reduce((total, current) => total + current);
const data =[{"id":1,"name":"root","filesSize":123456,"children":[{"id":2,"name":"child 1","filesSize":789654},{"id":3,"name":"child 2","filesSize":321123,"children":[{"id":4,"name":"child 3 - 1","filesSize":88888},{"id":5,"name":"child 3 - 2","filesSize":99999,"children":[{"id":6,"name":"child 4 - 1","filesSize":325941}]}]}]},
{"id":7,"name":"root","filesSize":654321,"children":[{"id":8,"name":"child 1","filesSize":978855},{"id":9,"name":"child 2","filesSize":123321,"children":[{"id":10,"name":"child 3 - 1","filesSize":11111},{"id":11,"name":"child 3 - 2","filesSize":66666,"children":[{"id":12,"name":"child 4 - 1","filesSize":18756}]}]}]}];
const totalFileSize = (objArr, prop) =>
objArr.map(obj => Object.entries(obj)
.reduce((sum, [key, val]) =>
key == prop ? sum + parseInt(val) :
Array.isArray(val) ? obj.total = sum + parseInt(totalFileSize(val, prop)) :
0, 0))
.reduce((total, current) => total + current);
console.log(`Input array is doubled at the root so this the total sum of 12 objects not 6 like OP example.`);
console.log(totalFileSize(data, "filesSize"));
console.log(data);
const data = [{
id: 1,
name: 'root',
filesSize: 123456,
children: [{
id: 2,
name: 'child 1',
filesSize: 789654,
},
{
id: 3,
name: 'child 2',
filesSize: 321123,
children: [{
id: 4,
name: 'child 3 - 1',
filesSize: 88888,
},
{
id: 5,
name: 'child 3 - 2',
filesSize: 99999,
children: [{
id: 99999,
name: 'child m - n',
filesSize: 12345,
}]
}]
}]
}];
function calculateTotals(d) {
let total = d.filesSize;
if (d.children)
d.children.forEach(c => total += calculateTotals(c));
return d.total = total;
}
data.forEach(d => calculateTotals(d));
console.log(data);
I am trying to delete all repeated objects between four arrays by preference. All the arrays have unique elements, and may not be ordered. Here is a picture that tries to explain the problem:
As you can see, if the array has a lower preference, the elements will stay inside it. For example, the object with id "6" is repeated in the arrays with preference 2, 3, and 4. So, the algorithm has to detect this and remove these objects from the arrays with preference 3 and 4, because 2 < 3 < 4.
So, if the input data is:
arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
arr_p2 = [{id: "saa1" }, { id: "892d" }]
arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }]
the output must be:
arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
arr_p2 = [{id: "saa1" }]
arr_p3 = [{id: "pp182" }]
Any ideas on how to solve this situation in a good complexity order?
All arrays have a limited size of 40 objects.
The only thing I can think of is to sort all the objects, in each array, by identifier. Then, take the lowest identifier of an object moving with the pointer of each list, from the lowest preference (1) to the highest (4), and if it is in one of the higher preference lists, delete it... but I need to do it without altering the order of the elements ...
Pd: I am using JS and ES6.
Combine all items to a single array, and then reduce them to a Map in a reversed order using Array.reduceRight(). The reversed order will cause the 1st items to override the last items.
Now you can filter each array by using the Map, and keeping only items that exist on the Map.
Complexity is O(N1 + N2 + N3) where Nx is the length of that array.
const arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
const arr_p2 = [{id: "saa1" }, { id: "892d" }]
const arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }]
// create an array of all items and reduce it in a reversed order to a Map
const dupsMap = [...arr_p1, ...arr_p2, ...arr_p3]
// create the Map by using the `id` as the key, and the object as the value
.reduceRight((acc, o) => acc.set(o.id, o), new Map())
const filterArr = arr => arr.filter(o =>
dupsMap.get(o.id) === o // keep the item if it was the object that was used as value
)
const arr_p1f = filterArr(arr_p1)
const arr_p2f = filterArr(arr_p2)
const arr_p3f = filterArr(arr_p3)
console.log({ arr_p1f, arr_p2f, arr_p3f })
You can easily create a generic function that can handle any number of arrays, and get the individual arrays from it's returned value using destructuring.
const dedupArrays = (...arrs) => {
const dupsMap = arrs.flat() // convert arrays to a single array
// a reduce right to create a Map of [id, object]
.reduceRight((acc, o) => acc.set(o.id, o), new Map())
// map the array of arrays, and filter each sub array
return arrs.map(arr => arr.filter(o => dupsMap.get(o.id) === o))
}
const arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
const arr_p2 = [{id: "saa1" }, { id: "892d" }]
const arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }]
const [arr_p1f, arr_p2f, arr_p3f] = dedupArrays(arr_p1, arr_p2, arr_p3)
console.log({ arr_p1f, arr_p2f, arr_p3f })
You could generate a preference object (hash map) to map the id to preference. Run it from 3rd array to the first so that lower order overrides the higher one.
Then when you have the preference map, you can filter all arrays by checking if the id's preference matches the current array.
let arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }];
let arr_p2 = [{id: "saa1" }, { id: "892d" }];
let arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }];
let pref = {};
arr_p3.forEach(e => pref[e.id] = 3);
arr_p2.forEach(e => pref[e.id] = 2);
arr_p1.forEach(e => pref[e.id] = 1);
arr_p1 = arr_p1.filter(e => pref[e.id] === 1);
arr_p2 = arr_p2.filter(e => pref[e.id] === 2);
arr_p3 = arr_p3.filter(e => pref[e.id] === 3);
console.log(arr_p1);
console.log(arr_p2);
console.log(arr_p3);
I have several tips for you, rather than a full answer, since I assume this is a homework question?
Strategy
Build a set of "items already seen"
Check each new array against that, deleting any duplicate entries (in the new array).
Start with the most preferred array
That way, whenever something is deleted, it is being deleted from the less-preferred array.
For example, in pseudocode
let elementsSeen = new Set( most preferred array of elements )
for array in listOfArraysInDecreasingOrderOfPreference {
for element in array {
if element is in elementsSeen, delete it from array
}
elementsSeen = union of elementsSeen and array
}
Complexity
Every item has to be looked at. It has to be compared with every other item, but the complexity of that need not be enormous, because the `Set` process can make use of hashes, i.e. not have to do an individual comparison of each incoming object with each existing object. Almost all incoming objects will have a hash table value that is different from those of existing objects, which is quick, at the expense of some time spent on hashing and some memory spent on the table.
In the worst case, where hashing is no longer helping you, it is O(N x M) where N is the number of arrays, and M is the size of each.
Your question implies you want to mutate the original arrays.
So if you still want to mutate the arrays you could.
create a SET of the ID's for each level.
Loop each level backward, if any id's in higher level then remove from array.
A couple of optimisation here too, eg. slice(0, -1), is so we don't need to create a SET for the last level, as were check previous ones. Inside the loop once item is known to be deleted, use a break to then go onto next. To be honest, I've no idea what the complexity on this is.. :)
eg.
const arr_p1 =
[{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }];
const arr_p2 =
[{id: "saa1" }, { id: "892d" }];
const arr_p3 =
[{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }];
function dedupe(alist) {
const hasList = alist.map(
m => new Set(m.slice(0, -1).map(i => i.id)));
for (let l = alist.length -1; l > 0; l --) {
for (let i = alist[l].length -1; i >= 0; i --) {
for (let h = 0; h < l; h += 1) {
if (hasList[h].has(alist[l][i].id)) {
alist[l].splice(i, 1);
break;
}
}
}
}
}
dedupe([arr_p1, arr_p2, arr_p3]);
console.log(arr_p1);
console.log(arr_p2);
console.log(arr_p3);
I have an array objects that hold an id and a name
const stages = [{
id: 1,
name: ''
}, {
id: 2,
name: ''
}, {
id: 3,
name: ''
}, {
id: 4,
name: ''
}, {
id: 5,
name: ''
}, {
id: 6,
name: ''
}, {
id: 7,
name: ''
}, {
id: 8,
name: ''
}];
Further I have an array that holds numbers.
const indexPositions = [0, 1, 2, 2, 2, 3, 2, 0];
I want to create a third array that holds arrays. Each number in distances represents the index of the current array within the array.
If the current array does not exist yet I want to create it first. Obviously I have to create new arrays until I get to this index position.
Example:
My array is empty at start. The first index position is 0 so I have to create a new array for this. The next index position is 3 so I have to create more arrays until I have 4 arrays.
All I want to do is to push the stage to its correct level index position. The result of this example would be
const levels = [
[stage1, stage8],
[stage2],
[stage3, stage4, stage5, stage7],
[stage6]
];
Currently my code looks this
$(document).ready(() => {
const levels = []; // the array containing the arrays
stages.forEach((stage, stageIndex) => {
const indexPosition = indexPositions[stageIndex];
const positionDifference = indexPosition - levels.length;
if (positionDifference > 0) {
for (let i = 0; i < positionDifference; i++) { // fill up with empty arrays
levels.push([]);
}
}
levels[indexPosition].push(stage);
});
});
I get this error Uncaught TypeError: Cannot read property 'push' of undefined and this happens because the indexPosition is out of bounds. If the positionDifference is 0 no array gets created but in the beginning the array is empty.
I tried setting levels.length to -1 if it is 0 but I still get the error if the difference is 1, I create one array at position 0 and want to access position 1.
How can I create an empty array if it does not exist?
While I do not fully understand what you want to do, checking existence of an array element is simple, one way of doing that is coercing it to boolean:
const thing=[];
function addElem(where,what){
if(!thing[where]) // <- here
thing[where]=[];
thing[where].push(what);
}
addElem(2,1);
addElem(2,2);
addElem(2,3);
addElem(5,1);
console.log(thing);
(The indices are deliberately non-continuous, because that does not matter: JavaScript arrays are sparse)
You could use a single loop and add an array for the index if not exists. Then push the wanted value.
var stages = [{ id: 1, name: '' }, { id: 2, name: '' }, { id: 3, name: '' }, { id: 4, name: '' }, { id: 5, name: '' }, { id: 6, name: '' }, { id: 7, name: '' }, { id: 8, name: '' }],
indexPositions = [0, 1, 2, 2, 2, 3, 2, 0],
result = stages.reduce((r, o, i) => {
var index = indexPositions[i];
r[index] = r[index] || []; // take default value for falsy value
r[index].push('stage' + o.id); // instead of string take object
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You actually were very close! You have a very small issue in your code.
$(document).ready(() => {
const levels = []; // the array containing the arrays
stages.forEach((stage, stageIndex) => {
const indexPosition = indexPositions[stageIndex];
const positionDifference = indexPosition - levels.length + 1; //YOU DID NOT ADD 1 HERE
if (positionDifference > 0) {
for (let i = 0; i < positionDifference; i++) { // fill up with empty arrays
levels.push([]);
}
}
levels[indexPosition].push(stage);
});
});
When you were calculating the positionDifference, you did not add 1 causing the problem when indexPosition equaled 0 and the for loop did not run and no new arrays were pushed. Just adding one fixed the problem :-)
This question already has answers here:
Merge property from an array of objects into another based on property value lodash
(5 answers)
Closed 4 years ago.
I have 2 array of objects
The first one called data:
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
]
and the second called subs:
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
]
In which I want to compare that if they have the same ID, the subs array will pass its name value to it and if it does not match that it puts a '-' in the data array, try this way:
data.forEach((d)=>{
subs.forEach((s)=>{
if(d.id === s.id){
d.subname = s.name;
}
else {
d.subname = '-';
}
});
});
But always assign the values with '-' as if it does not match any. What part am I doing wrong? Is there any other simpler way to do this? I would greatly appreciate your help.
The size of the subs array may vary.
It looks like you are not exiting the inner loop when a successful match is found.
In the first example where you are looking for a match for Piero, in your first iteration 1===1 and d.subname is correctly set to 'Temprano'. However, you then continue to compare the values- 1 !== 4 so Temprano is overwritten with '-', and 1 !== 7 so it is overwritten again.
An alternate approach:
data.forEach(d => {
const match = subs.find(s => s.id === d.id);
d.subname = match ? match.name : '-';});
I'd also recommend adding a case where you're not expecting to find a match, so you can see that it works in both cases!
https://codepen.io/anon/pen/MGGBLP?editors=0010
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
];
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
];
// by caching one of the arrays in an object, it reduces the run time to linear.
const obj = subs.reduce((acc, item) => {
acc[item.id] = item;
return acc;
})
data.forEach(d => {
if (d.id in obj) {
d.subname = obj[d.id].name;
} else {
d.subname = '-';
}
});
console.log(data);
You just need two lines for this:
var findIds = id => subs.find(findId => findId.id === id);
data.forEach(findId => Object.assign(findId, findIds(findId.id)));
Your data array object should now include the name property from it's respective id sharing object in subs array.
jsFiddle: https://jsfiddle.net/AndrewL64/9k1d3oj2/1/