Related
I want to group array of objects by its key,
The Original form;
data = [
{'id': 1, 'name': 'karthik'},
{'id': 1, 'age': 31},
{'id': 2, 'name': 'ramesh'},
{'id': 2, 'age': 22}
];
To transform in to,
groupedData = [
{'id': 1, 'name': 'karthik', 'age': 31},
{'id': 2, 'name': 'ramesh', 'age': 22}
];
What I tried,
this.data.map(item => item.id)
.filter((item, index, all) => all.indexOf(item) === index);
console.log(this.data);
Use reduce instead of map.
const groupedData = Object.values(this.data.reduce((a, { id, ...r }) => ({ ...a, [id]: { ...(a[id] || { id }), ...r }}), {}));
How this works:
Firstly, using reduce is far easier than any map solution because it allows us to have an accumulator value a.
Then, we extract the id, and the rest of the properties (because we don't know what they're called).
We return a, with the property keyed with the value of id being either the existing property or a new one with id, as well as the rest of the properties.
You can use reduce to create an object ( a table ) for each id.
const groupMap = data.reduce((group, currentData) => {
const id = currentData['id']
group[id] = { ...(group[id] || {}), ...currentData }
return group
} ,{})
what this returns is something like:
{
"1": {
"id": 1,
"name": "karthik",
"age": 31
},
"2": {
"id": 2,
"name": "ramesh",
"age": 22
}
}
group[id] = { ...(group[id] || {}), ...currentData } is basically "if you already saw this id, just merge the previous data with the current data"
then you can get the final result calling Object.values
const groupedData = Object.values(groupMap)
which just get the values of the object created above.
Try this
data = data.reduce((total, current, index) => {
for(let key in current) total[index][key] = current[key]
return total
}, [])
This function will help you)
function mergeobj(arrofobj) {
arrofobj.map((item, index) => {
for (let i = 0; i < arrofobj.length; i++) {
if(i==index)continue;
if (item['id'] == arrofobj[i]['id']) {
item = Object.assign(item, arrofobj[i]);
arrofobj.splice(i, 1);
--i;
}
}
return item;
})
return arrofobj; }
How do I validate the property through object? I have define the list of Property in the checkProperty
I expected missingFields to return Batch.Name is missing.
Currently is is outputting [ 'Batch.Id', 'Batch.Name' ] which is wrong.
let data = {
Batch: {
Id: 123,
},
Total: 100,
}
let checkProperty = ['Total', 'Batch.Id', 'Batch.Name'];
let missingFields = [];
checkProperty.forEach(field => {
if (!data[field]) {
missingFields.push(field);
}
});
console.log(missingFields);
You'll have to use something like reduce after splitting on dots to check whether the nested value exists:
let data = {
Batch: {
Id: 123,
},
Total: 100,
}
let checkProperty = ['Total', 'Batch.Id', 'Batch.Name'];
let missingFields = [];
checkProperty.forEach(field => {
const val = field.split('.').reduce((a, prop) => !a ? null : a[prop], data);
if (!val) {
missingFields.push(field);
}
});
console.log(missingFields);
You can use this
The reason why thisdata[field]when dodata[Batch.Id]it tries to check at the first level key of object. in our case we don't have any key such asBatch.Id.
For our case we need `data[Batch][Id]` something like this which first searches
for `Batch` property and than or the found value it searches for `Id`.
let data = {
Batch: {
Id: 123,
},
Total: 100,
}
let checkProperty = ['Total', 'Batch.Id', 'Batch.Name'];
let missingFields = [];
checkProperty.forEach(field => {
let temp = field.split('.').reduce((o,e)=> {
return o[e] || data[e]
},{});
if (!temp) {
missingFields.push(field);
}
});
console.log(missingFields);
If you can use additional libraries Ajv is perfect for this. Instead of creating all the logic by yourself, you can create a schema and validate it.
var schema = {
"type": "object",
"properties": {
"Batch": {
"type": "object",
"required": ["Id", "Name"],
"properties":
{
"Id":{},
"Name":{},
},
},
"Total": {}
}
};
let json = {
Batch: {
Id: 123,
},
Total: 100,
}
var ajv = new Ajv({
removeAdditional: 'all',
allErrors: true
});
var validate = ajv.compile(schema);
var result = validate(json);
console.log('Result: ', result);
console.log('Errors: ', validate.errors);
Returns the following error message:
dataPath:".Batch"
keyword:"required"
message:"should have required property 'Name'"
params:{missingProperty: "Name"}
schemaPath:"#/properties/Batch/required"
https://jsfiddle.net/95m7z4tw/
In an array of objects I need to find a value -- where key is activity : However the activity key can be deeply nested in the array like so:
const activityItems = [
{
name: 'Sunday',
items: [
{
name: 'Gym',
activity: 'weights',
},
],
},
{
name: 'Monday',
items: [
{
name: 'Track',
activity: 'race',
},
{
name: 'Work',
activity: 'meeting',
},
{
name: 'Swim',
items: [
{
name: 'Beach',
activity: 'scuba diving',
},
{
name: 'Pool',
activity: 'back stroke',
},
],
},
],
},
{} ...
{} ...
];
So I wrote a recursive algorithm to find out if a certain activity is in the array:
let match = false;
const findMatchRecursion = (activity, activityItems) => {
for (let i = 0; i < activityItems.length; i += 1) {
if (activityItems[i].activity === activity) {
match = true;
break;
}
if (activityItems[i].items) {
findMatchRecursion(activity, activityItems[i].items);
}
}
return match;
};
Is there an ES6 way of determining if an activity exists in an array like this?
I tried something like this:
const findMatch(activity, activityItems) {
let obj = activityItems.find(o => o.items.activity === activity);
return obj;
}
But this won't work with deeply nested activities.
Thanks
You can use some() method and recursion to find if activity exists on any level and return true/false as result.
const activityItems = [{"name":"Sunday","items":[{"name":"Gym","activity":"weights"}]},{"name":"Monday","items":[{"name":"Track","activity":"race"},{"name":"Work","activity":"meeting"},{"name":"Swim","items":[{"name":"Beach","activity":"scuba diving"},{"name":"Pool","activity":"back stroke"}]}]}]
let findDeep = function(data, activity) {
return data.some(function(e) {
if(e.activity == activity) return true;
else if(e.items) return findDeep(e.items, activity)
})
}
console.log(findDeep(activityItems, 'scuba diving'))
While not as elegant as a recursive algorithm, you could JSON.stringify() the array, which gives this:
[{"name":"Sunday","items":[{"name":"Gym","activity":"weights"}]},{"name":"Monday","items":[{"name":"Track","activity":"race"},{"name":"Work","activity":"meeting"},{"name":"Swim","items":[{"name":"Beach","activity":"scuba diving"},{"name":"Pool","activity":"back stroke"}]}]}]
You could then use a template literal to search for the pattern:
`"activity":"${activity}"`
Complete function:
findMatch = (activity, activityItems) =>
JSON.stringify(activityItems).includes(`"activity":"${activity}"`);
const activityItems = [{
name: 'Sunday',
items: [{
name: 'Gym',
activity: 'weights',
}, ],
},
{
name: 'Monday',
items: [{
name: 'Track',
activity: 'race',
},
{
name: 'Work',
activity: 'meeting',
},
{
name: 'Swim',
items: [{
name: 'Beach',
activity: 'scuba diving',
},
{
name: 'Pool',
activity: 'back stroke',
},
],
},
],
}
];
findMatch = (activity, activityItems) =>
JSON.stringify(activityItems).includes(`"activity":"${activity}"`);
console.log(findMatch('scuba diving', activityItems)); //true
console.log(findMatch('dumpster diving', activityItems)); //false
First, your function could be improved by halting once a match is found via the recursive call. Also, you're both declaring match outside, as well as returning it. Probably better to just return.
const findMatchRecursion = (activity, activityItems) => {
for (let i = 0; i < activityItems.length; i += 1) {
if (activityItems[i].activity === activity) {
return true;
}
if (activityItems[i].items && findMatchRecursion(activity, activityItems[i].items) {
return true;
}
}
return false;
};
There's no built in deep search, but you can use .find with a named function if you wish.
var result = !!activityItems.find(function fn(item) {
return item.activity === "Gym" || (item.items && item.items.find(fn));
});
We now use object-scan for simple data processing tasks like this. It's really good once you wrap your head around how to use it. Here is how one could answer your questions
// const objectScan = require('object-scan');
const find = (activity, input) => objectScan(['**'], {
abort: true,
rtn: 'value',
filterFn: ({ value }) => value.activity === activity
})(input);
const activityItems = [{"name":"Sunday","items":[{"name":"Gym","activity":"weights"}]},{"name":"Monday","items":[{"name":"Track","activity":"race"},{"name":"Work","activity":"meeting"},{"name":"Swim","items":[{"name":"Beach","activity":"scuba diving"},{"name":"Pool","activity":"back stroke"}]}]}]
console.log(find('scuba diving', activityItems));
// => { name: 'Beach', activity: 'scuba diving' }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
I have two arrays list1 and list2 which have objects with some properties; userId is the Id or unique property:
list1 = [
{ userId: 1234, userName: 'XYZ' },
{ userId: 1235, userName: 'ABC' },
{ userId: 1236, userName: 'IJKL' },
{ userId: 1237, userName: 'WXYZ' },
{ userId: 1238, userName: 'LMNO' }
]
list2 = [
{ userId: 1235, userName: 'ABC' },
{ userId: 1236, userName: 'IJKL' },
{ userId: 1252, userName: 'AAAA' }
]
I'm looking for an easy way to execute the following three operations:
list1 operation list2 should return the intersection of elements:
[
{ userId: 1235, userName: 'ABC' },
{ userId: 1236, userName: 'IJKL' }
]
list1 operation list2 should return the list of all elements from list1 which don't occur in list2:
[
{ userId: 1234, userName: 'XYZ' },
{ userId: 1237, userName: 'WXYZ' },
{ userId: 1238, userName: 'LMNO' }
]
list2 operation list1 should return the list of elements from list2 which don't occur in list1:
[
{ userId: 1252, userName: 'AAAA' }
]
You could define three functions inBoth, inFirstOnly, and inSecondOnly which all take two lists as arguments, and return a list as can be understood from the function name. The main logic could be put in a common function operation that all three rely on.
Here are a few implementations for that operation to choose from, for which you can find a snippet further down:
Plain old JavaScript for loops
Arrow functions using filter and some array methods
Optimised lookup with a Set
Plain old for loops
// Generic helper function that can be used for the three operations:
function operation(list1, list2, isUnion) {
var result = [];
for (var i = 0; i < list1.length; i++) {
var item1 = list1[i],
found = false;
for (var j = 0; j < list2.length && !found; j++) {
found = item1.userId === list2[j].userId;
}
if (found === !!isUnion) { // isUnion is coerced to boolean
result.push(item1);
}
}
return result;
}
// Following functions are to be used:
function inBoth(list1, list2) {
return operation(list1, list2, true);
}
function inFirstOnly(list1, list2) {
return operation(list1, list2);
}
function inSecondOnly(list1, list2) {
return inFirstOnly(list2, list1);
}
// Sample data
var list1 = [
{ userId: 1234, userName: 'XYZ' },
{ userId: 1235, userName: 'ABC' },
{ userId: 1236, userName: 'IJKL' },
{ userId: 1237, userName: 'WXYZ' },
{ userId: 1238, userName: 'LMNO' }
];
var list2 = [
{ userId: 1235, userName: 'ABC' },
{ userId: 1236, userName: 'IJKL' },
{ userId: 1252, userName: 'AAAA' }
];
console.log('inBoth:', inBoth(list1, list2));
console.log('inFirstOnly:', inFirstOnly(list1, list2));
console.log('inSecondOnly:', inSecondOnly(list1, list2));
Arrow functions using filter and some array methods
This uses some ES5 and ES6 features:
// Generic helper function that can be used for the three operations:
const operation = (list1, list2, isUnion = false) =>
list1.filter( a => isUnion === list2.some( b => a.userId === b.userId ) );
// Following functions are to be used:
const inBoth = (list1, list2) => operation(list1, list2, true),
inFirstOnly = operation,
inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);
// Sample data
const list1 = [
{ userId: 1234, userName: 'XYZ' },
{ userId: 1235, userName: 'ABC' },
{ userId: 1236, userName: 'IJKL' },
{ userId: 1237, userName: 'WXYZ' },
{ userId: 1238, userName: 'LMNO' }
];
const list2 = [
{ userId: 1235, userName: 'ABC' },
{ userId: 1236, userName: 'IJKL' },
{ userId: 1252, userName: 'AAAA' }
];
console.log('inBoth:', inBoth(list1, list2));
console.log('inFirstOnly:', inFirstOnly(list1, list2));
console.log('inSecondOnly:', inSecondOnly(list1, list2));
Optimising lookup
The above solutions have a O(n²) time complexity because of the nested loop -- some represents a loop as well. So for large arrays you'd better create a (temporary) hash on user-id. This can be done on-the-fly by providing a Set (ES6) as argument to a function that will generate the filter callback function. That function can then perform the look-up in constant time with has:
// Generic helper function that can be used for the three operations:
const operation = (list1, list2, isUnion = false) =>
list1.filter(
(set => a => isUnion === set.has(a.userId))(new Set(list2.map(b => b.userId)))
);
// Following functions are to be used:
const inBoth = (list1, list2) => operation(list1, list2, true),
inFirstOnly = operation,
inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);
// Sample data
const list1 = [
{ userId: 1234, userName: 'XYZ' },
{ userId: 1235, userName: 'ABC' },
{ userId: 1236, userName: 'IJKL' },
{ userId: 1237, userName: 'WXYZ' },
{ userId: 1238, userName: 'LMNO' }
];
const list2 = [
{ userId: 1235, userName: 'ABC' },
{ userId: 1236, userName: 'IJKL' },
{ userId: 1252, userName: 'AAAA' }
];
console.log('inBoth:', inBoth(list1, list2));
console.log('inFirstOnly:', inFirstOnly(list1, list2));
console.log('inSecondOnly:', inSecondOnly(list1, list2));
short answer:
list1.filter(a => list2.some(b => a.userId === b.userId));
list1.filter(a => !list2.some(b => a.userId === b.userId));
list2.filter(a => !list1.some(b => a.userId === b.userId));
longer answer:
The code above will check objects by userId value,
if you need complex compare rules, you can define custom comparator:
comparator = function (a, b) {
return a.userId === b.userId && a.userName === b.userName
};
list1.filter(a => list2.some(b => comparator(a, b)));
list1.filter(a => !list2.some(b => comparator(a, b)));
list2.filter(a => !list1.some(b => comparator(a, b)));
Also there is a way to compare objects by references
WARNING! two objects with same values will be considered different:
o1 = {"userId":1};
o2 = {"userId":2};
o1_copy = {"userId":1};
o1_ref = o1;
[o1].filter(a => [o2].includes(a)).length; // 0
[o1].filter(a => [o1_copy].includes(a)).length; // 0
[o1].filter(a => [o1_ref].includes(a)).length; // 1
Just use filter and some array methods of JS and you can do that.
let arr1 = list1.filter(e => {
return !list2.some(item => item.userId === e.userId);
});
This will return the items that are present in list1 but not in list2. If you are looking for the common items in both lists. Just do this.
let arr1 = list1.filter(e => {
return list2.some(item => item.userId === e.userId); // take the ! out and you're done
});
Use lodash's _.isEqual method. Specifically:
list1.reduce(function(prev, curr){
!list2.some(function(obj){
return _.isEqual(obj, curr)
}) ? prev.push(curr): false;
return prev
}, []);
Above gives you the equivalent of A given !B (in SQL terms, A LEFT OUTER JOIN B). You can move the code around the code to get what you want!
function intersect(first, second) {
return intersectInternal(first, second, function(e){ return e });
}
function unintersect(first, second){
return intersectInternal(first, second, function(e){ return !e });
}
function intersectInternal(first, second, filter) {
var map = {};
first.forEach(function(user) { map[user.userId] = user; });
return second.filter(function(user){ return filter(map[user.userId]); })
}
Here is a functionnal programming solution with underscore/lodash to answer your first question (intersection).
list1 = [ {userId:1234,userName:'XYZ'},
{userId:1235,userName:'ABC'},
{userId:1236,userName:'IJKL'},
{userId:1237,userName:'WXYZ'},
{userId:1238,userName:'LMNO'}
];
list2 = [ {userId:1235,userName:'ABC'},
{userId:1236,userName:'IJKL'},
{userId:1252,userName:'AAAA'}
];
_.reduce(list1, function (memo, item) {
var same = _.findWhere(list2, item);
if (same && _.keys(same).length === _.keys(item).length) {
memo.push(item);
}
return memo
}, []);
I'll let you improve this to answer the other questions ;-)
This is the solution that worked for me.
var intersect = function (arr1, arr2) {
var intersect = [];
_.each(arr1, function (a) {
_.each(arr2, function (b) {
if (compare(a, b))
intersect.push(a);
});
});
return intersect;
};
var unintersect = function (arr1, arr2) {
var unintersect = [];
_.each(arr1, function (a) {
var found = false;
_.each(arr2, function (b) {
if (compare(a, b)) {
found = true;
}
});
if (!found) {
unintersect.push(a);
}
});
return unintersect;
};
function compare(a, b) {
if (a.userId === b.userId)
return true;
else return false;
}
I have and object literal that is essentially a tree that does not have a fixed number of levels. How can I go about searching the tree for a particualy node and then return that node when found in an effcient manner in javascript?
Essentially I have a tree like this and would like to find the node with the title 'randomNode_1'
var data = [
{
title: 'topNode',
children: [
{
title: 'node1',
children: [
{
title: 'randomNode_1'
},
{
title: 'node2',
children: [
{
title: 'randomNode_2',
children:[
{
title: 'node2',
children: [
{
title: 'randomNode_3',
}]
}
]
}]
}]
}
]
}];
Basing this answer off of #Ravindra's answer, but with true recursion.
function searchTree(element, matchingTitle){
if(element.title == matchingTitle){
return element;
}else if (element.children != null){
var i;
var result = null;
for(i=0; result == null && i < element.children.length; i++){
result = searchTree(element.children[i], matchingTitle);
}
return result;
}
return null;
}
Then you could call it:
var element = data[0];
var result = searchTree(element, 'randomNode_1');
Here's an iterative solution:
var stack = [], node, ii;
stack.push(root);
while (stack.length > 0) {
node = stack.pop();
if (node.title == 'randomNode_1') {
// Found it!
return node;
} else if (node.children && node.children.length) {
for (ii = 0; ii < node.children.length; ii += 1) {
stack.push(node.children[ii]);
}
}
}
// Didn't find it. Return null.
return null;
Here's an iterative function using the Stack approach, inspired by FishBasketGordo's answer but taking advantage of some ES2015 syntax to shorten things.
Since this question has already been viewed a lot of times, I've decided to update my answer to also provide a function with arguments that makes it more flexible:
function search (tree, value, key = 'id', reverse = false) {
const stack = [ tree[0] ]
while (stack.length) {
const node = stack[reverse ? 'pop' : 'shift']()
if (node[key] === value) return node
node.children && stack.push(...node.children)
}
return null
}
This way, it's now possible to pass the data tree itself, the desired value to search and also the property key which can have the desired value:
search(data, 'randomNode_2', 'title')
Finally, my original answer used Array.pop which lead to matching the last item in case of multiple matches. In fact, something that could be really confusing. Inspired by Superole comment, I've made it use Array.shift now, so the first in first out behavior is the default.
If you really want the old last in first out behavior, I've provided an additional arg reverse:
search(data, 'randomNode_2', 'title', true)
My answer is inspired from FishBasketGordo's iterativ answer. It's a little bit more complex but also much more flexible and you can have more than just one root node.
/**searchs through all arrays of the tree if the for a value from a property
* #param aTree : the tree array
* #param fCompair : This function will receive each node. It's upon you to define which
condition is necessary for the match. It must return true if the condition is matched. Example:
function(oNode){ if(oNode["Name"] === "AA") return true; }
* #param bGreedy? : us true to do not stop after the first match, default is false
* #return an array with references to the nodes for which fCompair was true; In case no node was found an empty array
* will be returned
*/
var _searchTree = function(aTree, fCompair, bGreedy){
var aInnerTree = []; // will contain the inner children
var oNode; // always the current node
var aReturnNodes = []; // the nodes array which will returned
// 1. loop through all root nodes so we don't touch the tree structure
for(keysTree in aTree) {
aInnerTree.push(aTree[keysTree]);
}
while(aInnerTree.length > 0) {
oNode = aInnerTree.pop();
// check current node
if( fCompair(oNode) ){
aReturnNodes.push(oNode);
if(!bGreedy){
return aReturnNodes;
}
} else { // if (node.children && node.children.length) {
// find other objects, 1. check all properties of the node if they are arrays
for(keysNode in oNode){
// true if the property is an array
if(oNode[keysNode] instanceof Array){
// 2. push all array object to aInnerTree to search in those later
for (var i = 0; i < oNode[keysNode].length; i++) {
aInnerTree.push(oNode[keysNode][i]);
}
}
}
}
}
return aReturnNodes; // someone was greedy
}
Finally you can use the function like this:
var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, false);
console.log("Node with title found: ");
console.log(foundNodes[0]);
And if you want to find all nodes with this title you can simply switch the bGreedy parameter:
var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, true);
console.log("NodeS with title found: ");
console.log(foundNodes);
FIND A NODE IN A TREE :
let say we have a tree like
let tree = [{
id: 1,
name: 'parent',
children: [
{
id: 2,
name: 'child_1'
},
{
id: 3,
name: 'child_2',
children: [
{
id: '4',
name: 'child_2_1',
children: []
},
{
id: '5',
name: 'child_2_2',
children: []
}
]
}
]
}];
function findNodeById(tree, id) {
let result = null
if (tree.id === id) {
return tree;
}
if (Array.isArray(tree.children) && tree.children.length > 0) {
tree.children.some((node) => {
result = findNodeById(node, id);
return result;
});
}
return result;}
You have to use recursion.
var currChild = data[0];
function searchTree(currChild, searchString){
if(currChild.title == searchString){
return currChild;
}else if (currChild.children != null){
for(i=0; i < currChild.children.length; i ++){
if (currChild.children[i].title ==searchString){
return currChild.children[i];
}else{
searchTree(currChild.children[i], searchString);
}
}
return null;
}
return null;
}
ES6+:
const deepSearch = (data, value, key = 'title', sub = 'children', tempObj = {}) => {
if (value && data) {
data.find((node) => {
if (node[key] == value) {
tempObj.found = node;
return node;
}
return deepSearch(node[sub], value, key, sub, tempObj);
});
if (tempObj.found) {
return tempObj.found;
}
}
return false;
};
const result = deepSearch(data, 'randomNode_1', 'title', 'children');
This function is universal and does search recursively.
It does not matter, if input tree is object(single root), or array of objects (many root objects). You can configure prop name that holds children array in tree objects.
// Searches items tree for object with specified prop with value
//
// #param {object} tree nodes tree with children items in nodesProp[] table, with one (object) or many (array of objects) roots
// #param {string} propNodes name of prop that holds child nodes array
// #param {string} prop name of searched node's prop
// #param {mixed} value value of searched node's prop
// #returns {object/null} returns first object that match supplied arguments (prop: value) or null if no matching object was found
function searchTree(tree, nodesProp, prop, value) {
var i, f = null; // iterator, found node
if (Array.isArray(tree)) { // if entry object is array objects, check each object
for (i = 0; i < tree.length; i++) {
f = searchTree(tree[i], nodesProp, prop, value);
if (f) { // if found matching object, return it.
return f;
}
}
} else if (typeof tree === 'object') { // standard tree node (one root)
if (tree[prop] !== undefined && tree[prop] === value) {
return tree; // found matching node
}
}
if (tree[nodesProp] !== undefined && tree[nodesProp].length > 0) { // if this is not maching node, search nodes, children (if prop exist and it is not empty)
return searchTree(tree[nodesProp], nodesProp, prop, value);
} else {
return null; // node does not match and it neither have children
}
}
I tested it localy and it works ok, but it somehow won't run on jsfiddle or jsbin...(recurency issues on those sites ??)
run code :
var data = [{
title: 'topNode',
children: [{
title: 'node1',
children: [{
title: 'randomNode_1'
}, {
title: 'node2',
children: [{
title: 'randomNode_2',
children: [{
title: 'node2',
children: [{
title: 'randomNode_3',
}]
}]
}]
}]
}]
}];
var r = searchTree(data, 'children', 'title', 'randomNode_1');
//var r = searchTree(data, 'children', 'title', 'node2'); // check it too
console.log(r);
It works in http://www.pythontutor.com/live.html#mode=edit (paste the code)
no BS version:
const find = (root, title) =>
root.title === title ?
root :
root.children?.reduce((result, n) => result || find(n, title), undefined)
This is basic recursion problem.
window.parser = function(searchParam, data) {
if(data.title != searchParam) {
returnData = window.parser(searchParam, children)
} else {
returnData = data;
}
return returnData;
}
here is a more complex option - it finds the first item in a tree-like node with providing (node, nodeChildrenKey, key/value pairs & optional additional key/value pairs)
const findInTree = (node, childrenKey, key, value, additionalKey?, additionalValue?) => {
let found = null;
if (additionalKey && additionalValue) {
found = node[childrenKey].find(x => x[key] === value && x[additionalKey] === additionalValue);
} else {
found = node[childrenKey].find(x => x[key] === value);
}
if (typeof(found) === 'undefined') {
for (const item of node[childrenKey]) {
if (typeof(found) === 'undefined' && item[childrenKey] && item[childrenKey].length > 0) {
found = findInTree(item, childrenKey, key, value, additionalKey, additionalValue);
}
}
}
return found;
};
export { findInTree };
Hope it helps someone.
A flexible recursive solution that will work for any tree
// predicate: (item) => boolean
// getChildren: (item) => treeNode[]
searchTree(predicate, getChildren, treeNode) {
function search(treeNode) {
if (!treeNode) {
return undefined;
}
for (let treeItem of treeNode) {
if (predicate(treeItem)) {
return treeItem;
}
const foundItem = search(getChildren(treeItem));
if (foundItem) {
return foundItem;
}
}
}
return search(treeNode);
}
find all parents of the element in the tree
let objects = [{
id: 'A',
name: 'ObjA',
children: [
{
id: 'A1',
name: 'ObjA1'
},
{
id: 'A2',
name: 'objA2',
children: [
{
id: 'A2-1',
name: 'objA2-1'
},
{
id: 'A2-2',
name: 'objA2-2'
}
]
}
]
},
{
id: 'B',
name: 'ObjB',
children: [
{
id: 'B1',
name: 'ObjB1'
}
]
}
];
let docs = [
{
object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{
object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD2',
name: 'Typde Doc2'
}
},
{
object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}
},
{
object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx2',
name: 'Typde Doc x1'
}
},
{
object: {
id: 'A2',
name: 'docA2'
},
typedoc: {
id: 'TDx2',
name: 'Type de Doc x2'
}
},
{
object: {
id: 'A2-1',
name: 'docA2-1'
},
typedoc: {
id: 'TDx2-1',
name: 'Type de Docx2-1'
},
},
{
object: {
id: 'A2-2',
name: 'docA2-2'
},
typedoc: {
id: 'TDx2-2',
name: 'Type de Docx2-2'
},
},
{
object: {
id: 'B',
name: 'docB'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{
object: {
id: 'B1',
name: 'docB1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}
}
];
function buildAllParents(doc, objects) {
for (let o = 0; o < objects.length; o++) {
let allParents = [];
let getAllParents = (o, eleFinded) => {
if (o.id === doc.object.id) {
doc.allParents = allParents;
eleFinded = true;
return { doc, eleFinded };
}
if (o.children) {
allParents.push(o.id);
for (let c = 0; c < o.children.length; c++) {
let { eleFinded, doc } = getAllParents(o.children[c], eleFinded);
if (eleFinded) {
return { eleFinded, doc };
} else {
continue;
}
}
}
return { eleFinded };
};
if (objects[o].id === doc.object.id) {
doc.allParents = [objects[o].id];
return doc;
} else if (objects[o].children) {
allParents.push(objects[o].id);
for (let c = 0; c < objects[o].children.length; c++) {
let eleFinded = null;`enter code here`
let res = getAllParents(objects[o].children[c], eleFinded);
if (res.eleFinded) {
return res.doc;
} else {
continue;
}
}
}
}
}
docs = docs.map(d => buildAllParents(d, objects`enter code here`))
This is an iterative breadth first search. It returns the first node that contains a child of a given name (nodeName) and a given value (nodeValue).
getParentNode(nodeName, nodeValue, rootNode) {
const queue= [ rootNode ]
while (queue.length) {
const node = queue.shift()
if (node[nodeName] === nodeValue) {
return node
} else if (node instanceof Object) {
const children = Object.values(node)
if (children.length) {
queue.push(...children)
}
}
}
return null
}
It would be used like this to solve the original question:
getParentNode('title', 'randomNode_1', data[0])
Enhancement of the code based on "Erick Petrucelli"
Remove the 'reverse' option
Add multi-root support
Add an option to control the visibility of 'children'
Typescript ready
Unit test ready
function searchTree(
tree: Record<string, any>[],
value: unknown,
key = 'value',
withChildren = false,
) {
let result = null;
if (!Array.isArray(tree)) return result;
for (let index = 0; index < tree.length; index += 1) {
const stack = [tree[index]];
while (stack.length) {
const node = stack.shift()!;
if (node[key] === value) {
result = node;
break;
}
if (node.children) {
stack.push(...node.children);
}
}
if (result) break;
}
if (withChildren !== true) {
delete result?.children;
}
return result;
}
And the tests can be found at: https://gist.github.com/aspirantzhang/a369aba7f84f26d57818ddef7d108682
Wrote another one based on my needs
condition is injected.
path of found branch is available
current path could be used in condition statement
could be used to map the tree items to another object
// if predicate returns true, the search is stopped
function traverse2(tree, predicate, path = "") {
if (predicate(tree, path)) return true;
for (const branch of tree.children ?? [])
if (traverse(branch, predicate, `${path ? path + "/" : ""}${branch.name}`))
return true;
}
example
let tree = {
name: "schools",
children: [
{
name: "farzanegan",
children: [
{
name: "classes",
children: [
{ name: "level1", children: [{ name: "A" }, { name: "B" }] },
{ name: "level2", children: [{ name: "C" }, { name: "D" }] },
],
},
],
},
{ name: "dastgheib", children: [{ name: "E" }, { name: "F" }] },
],
};
traverse(tree, (branch, path) => {
console.log("searching ", path);
if (branch.name === "C") {
console.log("found ", branch);
return true;
}
});
output
searching
searching farzanegan
searching farzanegan/classes
searching farzanegan/classes/level1
searching farzanegan/classes/level1/A
searching farzanegan/classes/level1/B
searching farzanegan/classes/level2
searching farzanegan/classes/level2/C
found { name: 'C' }
In 2022 use TypeScript and ES5
Just use basic recreation and built-in array method to loop over the array. Don't use Array.find() because this it will return the wrong node. Use Array.some() instead which allow you to break the loop.
interface iTree {
id: string;
children?: iTree[];
}
function findTreeNode(tree: iTree, id: string) {
let result: iTree | null = null;
if (tree.id === id) {
result = tree;
} else if (tree.children) {
tree.children.some((node) => {
result = findTreeNode(node, id);
return result; // break loop
});
}
return result;
}
const flattenTree = (data: any) => {
return _.reduce(
data,
(acc: any, item: any) => {
acc.push(item);
if (item.children) {
acc = acc.concat(flattenTree(item.children));
delete item.children;
}
return acc;
},
[]
);
};
An Approach to convert the nested tree into an object with depth 0.
We can convert the object in an object like this and can perform search more easily.
The following is working at my end:
function searchTree(data, value) {
if(data.title == value) {
return data;
}
if(data.children && data.children.length > 0) {
for(var i=0; i < data.children.length; i++) {
var node = traverseChildren(data.children[i], value);
if(node != null) {
return node;
}
}
}
return null;
}