Unlimited Deep Search with lodash - javascript

I have a dynamic array of objects which has an unlimited nested array of items in it like below:
var items = [{
id: '7172hsdr',
item: {},
items: []
},
{
id: '5343rtas',
item: {},
items: [{
id: '4545nrhk',
item: [],
items: [{
id: 'kbkb1212',
item: [],
items: []
}]
}]
}]
I want to search in this collection and find an object with id kbkb1212. I prefer to do it with lodash.
The problem is sometimes I look for an object with id of 7172hsdr, and sometimes I need kbkb1212 for instance.
What I've done
I have used below function which search the first level.
var item = _.find(items, { id: '7172hsdr' });
It works fine, but If I need kbkb1212 it does not.

In plain Javascript you could use an interative and recursive approach for finding an item in a nested data structure.
function find(array, id) {
var object;
array.some(function f(a) {
if (a.id === id) {
object = a;
return true;
}
if (Array.isArray(a.items)) {
return a.items.some(f);
}
});
return object;
}
var items = [{ id: '7172hsdr', item: {}, items: [] }, { id: '5343rtas', item: {}, items: [{ id: '4545nrhk', item: [], items: [{ id: 'kbkb1212', item: [], items: [] }] }] }];
console.log(find(items, '7172hsdr'));
console.log(find(items, 'kbkb1212'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

It's not universal solution, but it works for your case
function deepFind(array, id) {
return array.reduce(function(result, arrayItem) {
if (result.length) return result;
if (arrayItem.id === id) return result.concat([arrayItem]);
return arrayItem.items.length ? deepFind(arrayItem.items, id) : result;
}, [])
}
console.log('one', deepFind(items, 'kbkb1212')[0]);
console.log('two', deepFind(items, '7172hsdr')[0]);
https://jsfiddle.net/v8kpr83b/3/

Pure Javascript solution using a custom recursive function:
var items = [{ id: '7172hsdr', item: {}, items: [] }, { id: '5343rtas', item: {}, items: [{ id: '4545nrhk', item: [], items: [{ id: 'kbkb1212', item: [], items: [] }] }] }];
function getObjById(items, id) {
var i = 0, o;
for (i = 0, len = items.length; i < len; i++) {
o = items[i];
if (o['id'] && o.id == id) {
return o;
} else if (o['items'] && Array.isArray(o.items) && o.items.length){
return getObjById(o.items, id);
}
}
}
console.log(getObjById(items, 'kbkb1212'));
console.log(getObjById(items, '7172hsdr'));

Related

How do I sort an object with children properties?

I have an object that represents a tree:
const obj = {
"1": {
id: "1",
children: ["1-1", "1-2"]
},
"1-1": {
id: "1-1",
children: ["1-1-1", "1-1-2"]
},
"1-2": {
id: "1-2",
children: []
},
"1-1-1": {
id: "1-1-1",
children: []
},
"1-1-2": {
id: "1-1-2",
children: []
}
};
The result is a list similar to:
<ul>
<li>
1
<ul>
<li>
1.1
<ul>
<li>1.1.1</li>
<li>1.1.2</li>
</ul>
</li>
<li>
1.2
</li>
</ul>
</li>
</ul>
What I need is to transform the object above to an array where items go in the order they do in the list representation, i.e. ['1', '1-1', '1-1-1', '1-1-2', '1-2']. Ids can be any so I can't rely on them. It's the order of items in the children property that matters.
Update
The final result should be ['1', '1-1', '1-1-1', '1-1-2', '1-2'] i.e. the order they come in the list from the top to the bottom.
I use DFS to parse. It can sort any depth data. (You can try the obj2)
const obj = {
"1": {
id: "1",
children: ["1-1", "1-2"]
},
"1-1": {
id: "1-1",
children: ["1-1-1", "1-1-2"]
},
"1-2": {
id: "1-2",
children: []
},
"1-1-1": {
id: "1-1-1",
children: []
},
"1-1-2": {
id: "1-1-2",
children: []
}
};
const obj2 = {
"2": {
id: "2",
children: ["2-1", "2-2", "2-3"]
},
"2-1": {
id: "2-1",
children: ["2-1-1", "2-1-2"]
},
"2-2": {
id: "2-2",
children: []
},
"2-3": {
id: "2-3",
children: []
},
"2-1-1": {
id: "2-1-1",
children: ["2-1-1-1", "2-1-1-2"]
},
"2-1-2": {
id: "2-1-2",
children: ["2-1-2-1"]
},
"2-1-1-1": {
id: "2-1-1-1",
children: []
},
"2-1-1-2": {
id: "2-1-1-2",
children: []
},
"2-1-2-1": {
id: "2-1-2-1",
children: []
},
};
/* DFS */
function sort(id) {
if (!sorted.includes(id)) {
sorted.push(id);
obj[id].children.forEach(sub => {
sort(sub);
});
}
}
/* MAIN */
let sorted = [];
for (let [id, value] of Object.entries(obj)) {
sort(id);
}
console.log(sorted.flat());
const obj={1:{id:"1",children:["1-1","1-2"]},"1-1":{id:"1-1",children:["1-1-1","1-1-2"]},"1-2":{id:"1-2",children:[]},"1-1-1":{id:"1-1-1",children:[]},"1-1-2":{id:"1-1-2",children:[]}};
const output = Object.keys(obj)
// remove every non root
Object.entries(obj).forEach(el => el[1].children.forEach(child => {
let index = output.indexOf(child)
if (index !== -1) {
output.splice(index, 1)
}
}))
for (let i = 0; i < output.length; i++) {
// for each get it's children
let children = obj[output[i]].children
// push them just behind it
output.splice(i + 1, 0, ...children)
}
console.log(output)
You could try a recursive call with the base condition to ignore the traversed node
const obj = {
"1": {
id: "1",
children: ["1-1", "1-2"],
},
"1-1": {
id: "1-1",
children: ["1-1-1", "1-1-2"],
},
"1-2": {
id: "1-2",
children: [],
},
"1-1-1": {
id: "1-1-1",
children: [],
},
"1-1-2": {
id: "1-1-2",
children: [],
},
}
function traverse(obj) {
const res = []
const traversed = {}
function getChildren(id) {
if (traversed[id]) {
return
}
res.push(id)
traversed[id] = true
obj[id].children.forEach((childId) => getChildren(childId))
}
for (const id in obj) {
getChildren(id)
}
return res
}
console.log(traverse(obj))
Hope this is what you are expecting ?
let ans = []
function recursiveCallObj(key){
!ans.includes(key) ? ans.push(key) : ""
for(let i=0; i< obj[key].children.length; i++){
if(!ans.includes(obj[key].children[i])){
recursiveCallObj(obj[key].children[i])
}
else{
return
}
}
}
for(let [key, value] of Object.entries(obj)){
if(value.children.length > 0){
recursiveCallObj(key)
}
else{
!ans.includes(key) ? ans.push(key) : ""
}
}
console.log(ans)

Search nested array of objects and return full parents as results in JavaScript

I've successfully written a recursive function to loop through a nested object and find the results. But I'm having a hard time adding the entire parent if its children pass the test. I have the following code:
const myObj = [
{
name: '1',
pages: [
{
name: '1.1',
pages: []
},
{
name: '1.2',
pages: []
},
]
},
{
name: '2',
pages: []
},
{
name: '3',
pages: []
}
]
function searchPages(searchQuery, obj) {
let searchResults = [];
for (let i = 0; i < obj.length; i++) {
let item = searchString(obj[i], searchQuery);
if (item) {
searchResults.push(item);
}
}
return searchResults;
}
function searchString(obj, string) {
if (obj.name.includes(string)) {
return obj;
}
for (let i = 0; i < obj.pages.length; i++) {
const possibleResult = searchString(obj.pages[i], string);
if (possibleResult) {
return possibleResult;
}
}
}
let searchResults = searchPages('1.1', myObj);
console.log(searchResults);
This searches the nested array properly and gives the correct result:
{
"name": "1.1",
"pages": []
}
But I would like to return the entire parent object, instead of just the child object. So the expected result is this:
{
name: '1',
pages: [
{
name: '1.1',
pages: []
},
{
name: '1.2',
pages: []
},
]
}
How can I modify my function to achieve this?
Keep in mind this is just a small object just for readability purposes. My actual object will have many more levels and properties.
You could take a recursive approach and check if the nested arrays have the wanted name.
function searchPages(array, string) {
const find = ({ name, pages }) => name.includes(string) || pages && pages.some(find);
return array.filter(find);
}
const
data = [{ name: '1', pages: [{ name: '1.1', pages: [] }, { name: '1.2', pages: [] }] }, { name: '2', pages: [] }, { name: '3', pages: [] }],
searchResults = searchPages(data, '1.1');
console.log(searchResults);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's my approach with .filter-ing myObj and .find-ing nested pages with given name
const myObj = [
{
name: '1',
pages: [
{
name: '1.1',
pages: []
},
{
name: '1.2',
pages: []
},
]
},
{
name: '2',
pages: []
},
{
name: '3',
pages: []
}
];
const searchPages = (name, arr) => arr.filter(
({ pages }) => pages.find(page => page.name === name)
)
let searchResults = searchPages('1.1', myObj);
console.log(searchResults);
If you want to return the parent object instead of the searched object, you just need to change the searchString() implementation so it takes the parent as third param, and then return it if you find the desired string:
function searchPages(searchQuery, obj) {
let searchResults = [];
for (let i = 0; i < obj.length; i++) {
let item = searchString(obj[i], searchQuery, obj);
if (item) {
searchResults.push(item);
}
}
return searchResults;
}
function searchString(obj, string, parent) {
if (obj.name.includes(string)) {
return parent;
}
for (let i = 0; i < obj.pages.length; i++) {
const possibleResult = searchString(obj.pages[i], string, obj);
if (possibleResult) {
return possibleResult;
}
}
}
This way you will always take the parent into account.
Demo:
const myObj = [
{
name: '1',
pages: [
{
name: '1.1',
pages: [
{
name: '1.1.1',
pages: []
}
]
},
{
name: '1.2',
pages: []
},
]
},
{
name: '2',
pages: []
},
{
name: '3',
pages: []
}
]
function searchPages(searchQuery, obj) {
let searchResults = [];
for (let i = 0; i < obj.length; i++) {
let item = searchString(obj[i], searchQuery, obj);
if (item) {
searchResults.push(item);
}
}
return searchResults;
}
function searchString(obj, string, parent) {
if (obj.name.includes(string)) {
return parent;
}
for (let i = 0; i < obj.pages.length; i++) {
const possibleResult = searchString(obj.pages[i], string, obj);
if (possibleResult) {
return possibleResult;
}
}
}
let searchResults = searchPages('1.1.1', myObj);
console.log(searchResults);
Here's one possible approach, using .filter for the top array, followed by recursive calls of .some:
const myObj = [
{
name: '1',
pages: [
{
name: '1.1',
pages: []
},
{
name: '1.2',
pages: []
},
]
},
{
name: '2',
pages: []
},
{
name: '3',
pages: []
}
];
const searchPages = (nameToFind, obj) => obj.filter(pageContainsName(nameToFind));
const pageContainsName = nameToFind => ({ name, pages }) => (
name === nameToFind || pages.some(pageContainsName(nameToFind))
);
let searchResults = searchPages('1.1', myObj);
console.log(searchResults);

Find index of a dynamic multidimensional array/json with matches id

thanks for checking,
I have a dynamic array which will contain multiple item/object.
I want the index number of this array, if a provided id matches with one of its contained
But Because it is a dynamically generated array/json
it can have any amount of multidimensional array inside child items and so on and so forth.
so is there any way to find the index number with matches id.
var data = [
{
id:1,
child:[
{
id: 2,
child: [
{
id: 3,
child: []
},
{
id:4,
child:[
{
id:44,
child:[
{
id:55,
child:[]
}
]
}
]
}
]
},
{
id:5,
child:[
{
id:6,
child:[]
}
]
}
]
}
]
Suppose i want to get the index of array where id is equal to 4.
I need to develop a logic/function which will return -> data[0]['child'][0]['child'][1]
Do it recursively
function findId(obj, id, currentPath = "") {
// Go through every object in the array
let i = 0;
for (let child of obj) {
// If id matches, return
if (child.id == id) return currentPath + `[${i}]`;
// Else go through the children, if we find anything there, return it
let next = findId(child.child, id, currentPath + `[${i}]['child']`);
if (next) return next;
i++;
}
// We didn't find anything
return null;
}
You could take a complete dynmaic approach with knowing some keys.
function findPath(object, id) {
var path;
if (!object || typeof object !== 'object') return;
if (object.id === id) return [];
Object.entries(object).some(([k, o]) => {
var temp;
if (temp = findPath(o, id, path = [])) {
path = [k, ...temp];
return true;
}
});
return path;
}
var data = [{ id: 1, child: [{ id: 2, child: [{ id: 3, child: [] }, { id: 4, child: [{ id: 44, child: [{ id: 55, child: [] }] }] }] }, { id: 5, child: [{ id: 6, child: [] }] }] }];
console.log(findPath(data, 44));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Constructing alphabetized array of objects

I have an array of objects with a name property...
var myList = [{
name: 'Apple'
}, {
name: 'Nervousness',
}, {
name: 'Dry'
}, {
name: 'Assign'
}, {
name: 'Date'
}]
Essentially, I am trying to create an array set up like this:
[{
name: 'A',
items: [{
name: 'Apple'
}, {
name: 'Assign'
}]
}, {
name: 'D',
items: [{
name: 'Date',
}, {
name: 'Dry',
}]
}, {
name: 'N',
items: [{
name: 'Nervousness',
}]
}];
Basically, my array of objects needs to be alphabetized, placed into a new object with a parent key/value of 'name' with the corresponding letter.
I can alphabetize them as follows...
myList.sort(function (a, b) {
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
});
Then I can create an array of the first letters...
var headerLetters = [];
angular.forEach(myList, function (item) {
var firstLetter = item.name.charAt(0);
if (headerLetters.indexOf(firstLetter) === -1) {
headerLetters.push(firstLetter);
}
});
But then this is where I am stuck... I can check for duplicate first letters, but then how would I iterate through my list of objects and push them into a new object array in alphabetical order?
Assuming you sort them alphabetically first then you can always just check the latest item in the array and see if it matches the current name.
var headerLetters = [];
angular.forEach(myList, function(item) {
var firstLetter = item.name[0];
var lastObj = headerLetters[headerLetters.length - 1];
if (!lastObj || lastObj.name !== firstLetter) {
lastObj = {
name: firstLetter,
items: []
};
headerLetters.push(lastObj);
}
lastObj.items.push(item);
});

Search deep in array and delete

I got the following array:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
}
}
]
}
];
The objects directly in the array are the groups. The 1: is the first language, 2: is second etc. The id is stored in every language object (due to the database I'm using). The children array is built the same way as the 'arr' array.
Example of multiple children:
var arr = [
{
1: {
id: 1,
title: 'test'
},
children: [
{
1: {
id: 2,
title: 'test2'
},
children: [
{
1: {
id: 3,
title: 'test3',
},
children: []
}
]
}
]
}
];
Now I need to delete items from this array. You can have unlimited children (I mean, children can have children who can have children etc.). I have a function which needs an ID parameter sent. My idea is to get the right object where the ID of language 1 is the id parameter. I got this:
function deleteFromArray(id)
{
var recursiveFunction = function (array)
{
for (var i = 0; i < array.length; i++)
{
var item = array[i];
if (item && Number(item[1].ID) === id)
{
delete item;
}
else if (item && Number(item[1].ID) !== id)
{
recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
}
However, I'm deleting the local variable item except for the item in the array. I don't know how I would fix this problem. I've been looking all over the internet but haven't found anything.
This proposal features a function for recursive call and Array.prototype.some() for the iteration and short circuit if the id is found. Then the array is with Array.prototype.splice() spliced.
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function splice(array, id) {
return array.some(function (a, i) {
if (a['1'].id === id) {
array.splice(i, 1)
return true;
}
if (Array.isArray(a.children)) {
return splice(a.children, id);
}
});
}
splice(arr, 2);
document.write('<pre>' + JSON.stringify(arr, 0, 4) + '</pre>');
var arr = [{ 1: { id: 1, title: 'test' }, children: [{ 1: { id: 2, title: 'test2' }, children: [{ 1: { id: 3, title: 'test3', }, children: [] }] }] }];
function deleteFromArray(id) {
function recursiveFunction(arr) {
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
if (item && Number(item[1].id) === id) {
arr.splice(i, 1);
} else if (item && Number(item[1].id) !== id) {
item.children && recursiveFunction(item.children);
}
}
};
recursiveFunction(arr);
};
deleteFromArray(2);
document.getElementById("output").innerHTML = JSON.stringify(arr, 0, 4);
<pre id="output"></pre>
jsfiddle: https://jsfiddle.net/x7mv5h4j/2/
deleteFromArray(2) will make children empty and deleteFromArray(1) will make arr empty itself.

Categories

Resources