Iterating and searching for element in n-dimensional nested array - javascript

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.

Related

Find duplicate names within an array of different files

A bit of a different use case from the ones I was suggested above.
I need to loop through and check each file name within an array of files and push the files that have the same name into a new array so that I can upload them later separately.
This is my code so far, and surely I have a problem with my conditional checking, can somebody see what I am doing wrong?
filesForStorage = [
{id: 12323, name: 'name', ...},
{id: 3123, name: 'abc', ...},
{id: 3213, name: 'name', ...},
...
]
filesForStorage.map((image, index) => {
for (let i = 0; i < filesForStorage.length; i++) {
for (let j = 0; j < filesForStorage.length; j++) {
if (
filesForStorage[i].name.split(".", 1) ===. //.split('.', 1) is to not keep in consideration the file extension
filesForStorage[j].name.split(".", 1)
) {
console.log(
"----FILES HAVE THE SAME NAME " +
filesForStorage[i] +
" " +
filesForStorage[j]
);
}
}
}
Using map without returning anything makes it near on pointless. You could use forEach but that is equally pointless when you're using a double loop within - it means you would be looping once in the foreach (or map in your case) and then twice more within making for eye-wateringly bad performance.
What you're really trying to do is group your items by name and then pick any group with more than 1 element
const filesForStorage = [
{id: 12323, name: 'name'},
{id: 3123, name: 'abc'},
{id: 3213, name: 'name'}
]
const grouped = Object.values(
filesForStorage.reduce( (a,i) => {
a[i.name] = a[i.name] || [];
a[i.name].push(i);
return a;
},{})
);
console.log(grouped.filter(x => x.length>1).flat());
JavaScript has several functions which perform "hidden" iteration.
Object.values will iterate through an object of key-value pairs and collect all values in an array
Array.prototype.reduce will iterate through an array and perform a computation for each element and finally return a single value
Array.prototype.filter will iterate through an array and collect all elements that return true for a specified test
Array.prototype.flat will iterate through an array, concatenating each element to the next, to create a new flattened array
All of these methods are wasteful as you can compute a collection of duplicates using a single pass over the input array. Furthermore, array methods offer O(n) performance at best, compared to O(1) performance of Set or Map, making the choice of arrays for this kind of computation eye-wateringly bad -
function* duplicates (files) {
const seen = new Set()
for (const f of files) {
if (seen.has(f.name))
yield f
else
seen.add(f.name, f)
}
}
const filesForStorage = [
{id: 12323, name: 'foo'},
{id: 3123, name: 'abc'},
{id: 3213, name: 'foo'},
{id: 4432, name: 'bar'},
{id: 5213, name: 'qux'},
{id: 5512, name: 'bar'},
]
for (const d of duplicates(filesForStorage))
console.log("duplicate name found", d)
duplicate name found {
"id": 3213,
"name": "foo"
}
duplicate name found {
"id": 5512,
"name": "bar"
}
A nested loop can be very expensive on performance, especially if your array will have a lot of values. Something like this would be much better.
filesForStorage = [
{ id: 12323, name: 'name' },
{ id: 3123, name: 'abc' },
{ id: 3213, name: 'name' },
{ id: 3123, name: 'abc' },
{ id: 3213, name: 'name' },
{ id: 3123, name: 'random' },
{ id: 3213, name: 'nothing' },
]
function sameName() {
let checkerObj = {};
let newArray = [];
filesForStorage.forEach(file => {
checkerObj[file.name] = (checkerObj[file.name] || 0) + 1;
});
Object.entries(checkerObj).forEach(([key, value]) => {
if (value > 1) {
newArray.push(key);
}
});
console.log(newArray);
}
sameName();

ES6 filter - how to return an object instead of an array?

I have bunch of array of object, I want to get particular object using filter, but I got array using below code.
const target = [{
name: 'abc',
id: 1
}, {
name: 'def',
id: 2
}]
const x = target.filter(o => o.id === 1)
console.log(x)
As said in the comments, filter won't allow you to get a particular object from an array - it just returns another array which elements satisfy the given predicate. What you actually need is Array.prototype.find(). Quoting the doc:
The find() method returns the value of the first element in the array
that satisfies the provided testing function. Otherwise undefined is
returned.
So your code looks like this:
const target = [{
name: 'abc',
id: 1
}, {
name: 'def',
id: 2
}];
const x = target.find(o => o.id === 1);
console.log(x); // {name: "abc", id: 1}
array.filter always return array. But you can try this-
const target = [{
name: 'abc',
id: 1
}, {
name: 'def',
id: 2
}]
let obj = {}
const x = target.filter( (o, index) => {
if(o.id === 1)
obj = target[index]
})
console.log(obj)
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
The find() method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.
Array.prototype.filter will return array containing elements from original array that passed test function.
If you are sure that id's are unique simply do x[0] to get result.
It's very easy just get first item in retrned as:
const target = [{name: 'abc', id: 1}, {name: 'def', id: 2}]
const x = target.filter(o => o.id === 1)
console.log(x[0])

JS/ES6: Check if every array element has a child in another array

There are three object arrays like this:
sections = [{ _id: '123'}]
groups = [{ _id: '456', parent: '123' }]
items = [{ _id: '789', parent: '456' }]
This is a valid dataset. Of course there are multiple objects in the arrays.
Now I want to check if every section has a minimum of one child group and every group have minimum one item.
If this check fails, false value should be returned.
Example
sections = [{ _id: '123'}]
groups = [{ _id: '456', parent: '123' }]
items = [{ _id: '789', parent: 'something' }]
complete = false
In this example false should be returned, as there is no child item for the group.
I tried to start with a forEach-loop, but this is a wrong attempt:
let complete = true
sections.forEach(s => {
if (groups.filter(g => { return g.parent === s._id }).length === 0)
complete = false
})
It looks like you have a three arrays. Two contain objects that serve as parent elements, and two contain objects that serve as child elements. You want to check whether every parent in the list of parents has a defined child.
This functionality can be achieved using a helper function everyParentHasChild(parents, children), which is built on the higher-level array methods Array#every and Array#some.
let sections = [{ _id: '123'}]
let groups = [{ _id: '456', parent: '123' }]
let items = [{ _id: '789', parent: '456' }]
let everyParentHasChild = (parents, children) => parents.every(
parent => children.some(child => child.parent === parent._id)
)
let result = everyParentHasChild(sections, groups) && everyParentHasChild(groups, items)
console.log(result) //=> true
const sections = [{ _id: '123'}];
const groups = [{ _id: '456', parent: '123' }];
const items = [{ _id: '789', parent: 'something' }];
const isComplete = function() {
// check sections
for (const section of sections) {
if (!groups.filter(group => group.parent == section._id).length) {
return false;
}
}
// check groups
for (const group of groups) {
if (!items.filter(item => item.parent == group._id).length) {
return false;
}
}
return true;
};
console.log(isComplete());

Set depth level properity in flattened object hierarchy

Given this unordered flat object array
var obj = [
{
id: 1,
parentId: null
},
{
id: 2,
parentId: 1
},
...
}];
I want to set a property $level according to the hierarchy depth level. (There is no max depth)
So the result would be
var obj = [
{
id: 1,
parentId: null,
$level = 0
},
{
id: 2,
parentId: 1,
$level = 1
},
...
}];
I was thinking of writing a recursive function for this that would traverse the array and find the matching parentId == id and then if that also had a parent then again find the matching... while keeping a count of how many calls it took to reach a root. But that's a lot of array iteration. It just seems bad.
So figured that this was a solved problem. But searching didn't give me anything useful. So I came here :)
How would you solve this?
Basically there is setLevel(parentId, level), a recursive function. The start values are null and level zero. The function has two parts. First get all rows which parentId equals to the given parentId. Then iterate throu the rows and assigns the level and calls itselft with the id as new value for parentId and a increased level.
var obj = [
{
id: 2,
parentId: 1
},
{
id: 1,
parentId: null
},
{
id: 5,
parentId: 4
},
{
id: 4,
parentId: 2
},
{
id: 3,
parentId: 2
},
];
function setLevel(parentId, level) {
obj.filter(function (el) {
return el.parentId === parentId;
}).forEach(function (el) {
el.$level = level;
setLevel(el.id, level + 1);
});
}
setLevel(null, 0);
document.write('<pre>'+ JSON.stringify(obj, null, 4) + '</pre>');
You don't have much choice for the solution in my opinion.
For each element, you have to traverse the array, searching for the number of ancestors for the element, the recursive or the iterative way.
In this case, you just search for an obj with the correct id.
You can't just call for the direct $level of the parent because your array is not ordered, then the parent's $level is not always set.

How can I get a unique array based on object property using underscore

I have an array of objects and I want to get a new array from it that is unique based only on a single property, is there a simple way to achieve this?
Eg.
[ { id: 1, name: 'bob' }, { id: 1, name: 'bill' }, { id: 1, name: 'bill' } ]
Would result in 2 objects with name = bill removed once.
Use the uniq function
var destArray = _.uniq(sourceArray, function(x){
return x.name;
});
or single-line version
var destArray = _.uniq(sourceArray, x => x.name);
From the docs:
Produces a duplicate-free version of the array, using === to test object equality. If you know in advance that the array is sorted, passing true for isSorted will run a much faster algorithm. If you want to compute unique items based on a transformation, pass an iterator function.
In the above example, the function uses the objects name in order to determine uniqueness.
If you prefer to do things yourself without Lodash, and without getting verbose, try this uniq filter with optional uniq by property:
const uniqFilterAccordingToProp = function (prop) {
if (prop)
return (ele, i, arr) => arr.map(ele => ele[prop]).indexOf(ele[prop]) === i
else
return (ele, i, arr) => arr.indexOf(ele) === i
}
Then, use it like this:
const obj = [ { id: 1, name: 'bob' }, { id: 1, name: 'bill' }, { id: 1, name: 'bill' } ]
obj.filter(uniqFilterAccordingToProp('abc'))
Or for plain arrays, just omit the parameter, while remembering to invoke:
[1,1,2].filter(uniqFilterAccordingToProp())
If you want to check all the properties then
lodash 4 comes with _.uniqWith(sourceArray, _.isEqual)
A better and quick approach
var table = [
{
a:1,
b:2
},
{
a:2,
b:3
},
{
a:1,
b:4
}
];
let result = [...new Set(table.map(item => item.a))];
document.write(JSON.stringify(result));
Found here
You can use the _.uniqBy function
var array = [ { id: 1, name: 'bob' }, { id: 2, name: 'bill' }, { id: 1, name: 'bill' },{ id: 2, name: 'bill' } ];
var filteredArray = _.uniqBy(array,function(x){ return x.id && x.name;});
console.log(filteredArray)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>
In the above example, filtering is based on the uniqueness of combination of properties id & name.
if you have multiple properties for an object.
then to find unique array of objects based on specific properties, you could follow this method of combining properties inside _.uniqBy() method.
I was looking for a solution which didn't require a library, and put this together, so I thought I'd add it here. It may not be ideal, or working in all situations, but it's doing what I require, so could potentially help someone else:
const uniqueBy = (items, reducer, dupeCheck = [], currentResults = []) => {
if (!items || items.length === 0) return currentResults;
const thisValue = reducer(items[0]);
const resultsToPass = dupeCheck.indexOf(thisValue) === -1 ?
[...currentResults, items[0]] : currentResults;
return uniqueBy(
items.slice(1),
reducer,
[...dupeCheck, thisValue],
resultsToPass,
);
}
const testData = [
{text: 'hello', image: 'yes'},
{text: 'he'},
{text: 'hello'},
{text: 'hell'},
{text: 'hello'},
{text: 'hellop'},
];
const results = uniqueBy(
testData,
item => {
return item.text
},
)
console.dir(results)
In case you need pure JavaScript solution:
var uniqueProperties = {};
var notUniqueArray = [ { id: 1, name: 'bob' }, { id: 1, name: 'bill' }, { id: 1, name: 'bill' } ];
for(var object in notUniqueArray){
uniqueProperties[notUniqueArray[object]['name']] = notUniqueArray[object]['id'];
}
var uniqiueArray = [];
for(var uniqueName in uniqueProperties){
uniqiueArray.push(
{id:uniqueProperties[uniqueName],name:uniqueName});
}
//uniqiueArray
unique array by id property with ES6:
arr.filter((a, i) => arr.findIndex(b => b.id === a.id) === i); // unique by id
replace b.id === a.id with the relevant comparison for your case

Categories

Resources