I am facing an algorithmic conception problem. With JavaScript language, I have an heavy JSON object of about 11 000 lines, that is the result of the conversion of an HTML file. The structure of the JSON is similar to the one of the DOM, which means that an Object can have a property children, a data structure composed of other similar Object. The goal is to search in the JSON and extract the information of the property itemprop of the Object that has that property. The itemprop attribute is in and Object inside the attributes attribute that some of the first mentioned Object have.
Object Structure
{ type: 'x',
tagName: 'y',
attributes: { "itemprop" : "valueWanted" },
children:
[ Object, Object, Object]
}
I thought of a recursive algorithm for solution. Unfortunately, I am not familiar with recursion and the next code is not working.
Recursive Algorithm
var searchAttributesRecursive = function(children) {
for (var i = 0; i < children.length; ++i) {
if (children[i].hasOwnProperty('children')) {
return searchAttributesRecursive(children[i].children);
}
else {
if (children[i].hasOwnProperty('attributes')) {
if (children[i].attributes.itemprop === "valueWanted") {
console.log('success')
}
}
}
return; // probably a problem that breaks the loop
}
};
searchAttributesRecursive(startingChildren);
There is maybe another more effective generic algorithms to get this task done. I am open to suggestions.
Update
Thank you for all solutions and explanation provided. More particularly, have a look to #ChrisG's simple solution. Now, I would like to add a special condition in the algorithm.
If I would like to retrieve the data from the next object, outside of the scope of the children where an object has the wantedValue2, do you have an idea how I can access this data? The algorithm would have a special case where it meets wantedValue2, and don't want to extract directly the data of itemprop.
Object Structure Special Case
{
"type": "",
"tagName": "",
"attributes": {
"itemprop": "wantedValue"
},
"children": [{
"type": "",
"content": ""
}
]
},
{
"type": "",
"content": ""
}]
},
{
"type": "",
"tagName": "",
"attributes": {},
"children": [
{
"type": "",
"content": "here"
}
]
Here's a shorter version:
Note that the function expects an array, so if your object is not an array, you have to use findItemprop([dom], "wanted")
function findItemprop(data, value, found) {
if (!found) found = [];
data.forEach((node) => {
if (node.attributes && node.attributes.itemprop == value)
found.push(node);
if (node.children) findItemprop(node.children, value, found);
});
return found;
}
var dom = [{
tag: "root",
children: [{
tag: "header",
children: [{
tag: "div"
}]
}, {
tag: "div",
id: "main",
children: [{
tag: "p",
attributes: {
itemprop: "wanted"
}
}]
}, {
tag: "footer",
children: [{
tag: "span",
content: "copyright 2017",
attributes: {
itemprop: "wanted"
}
}]
}]
}];
console.log(findItemprop(dom, "wanted"));
Your return will break the loop. You just want to return if it does return:
var searchAttributesRecursive = function(children) {
for (var i = 0; i < children.length; ++i) {
if (children[i].hasOwnProperty('children')) {
var result=searchAttributesRecursive(children[i].children);
if(result) return result;//if weve found sth, return
}
if (children[i].hasOwnProperty('attributes')) {
if (children[i].attributes.itemprop === "valueWanted1") {
console.log('success')
return children[i];//return sth useful
}
}
}
return false;//nothing found in this and in all childs
};
var elem=searchAttributesRecursive(startingChildren);
This returns the first found child. You may want to return an array instead:
var searchAttributesRecursive = function(children,result=[]) {
for (var i = 0; i < children.length; ++i) {
if (children[i].hasOwnProperty('children')) {
searchAttributesRecursive(children[i].children,result);
}
if (children[i].hasOwnProperty('attributes')) {
if (children[i].attributes.itemprop === "valueWanted1") {
console.log('success')
result.push(children[i]);//return sth useful
}
}
}
return result;//return all results found
};
var arr=searchAttributesRecursive(allElems);
arr.forEach(console.log);
Through passing an array as optional parameter it is fast and easy to store the traversal of multiple trees in one result:
var arr=[];
searchAttributesRecursive(allElems,arr);
searchAttributesRecursive(allElemsTwo,arr);
Give Jonas w the credit for their answer, I'm just tagging on to help correct some of the confusion surrounding the recursion, and hopefully make the it a little easier to understand and work with.
First, you're passing in the array of children. That's fine, but then you have to access each one from its array index as you check them. My recommendation is to make your function handle only one item at a time. (I'm going to use Jonas w's method of collecting nodes, because there may be more than one node with this attribute. I'm also going to add the attribute name as a parameter to make it a little more dynamic.)
function searchAttributesRecursive(currentNode, parameterName, results=[]){
}
Now you can concentrate on one and only one node at a time. Once it has passed the check, you can move on to the children.
function searchAttributesRecursive(currentNode, parameterName, results=[]){
if(currentNode.attributes && currentNode.attributes[parameterName]){
results.push(currentNode);
}
if(currentNode.children){
for(var i = 0, len = currentNode.children.length; i < len; ++i){
searchAttributesRecursive(currentNode.children[i], parameterName, results);
}
}
}
Calling it like this:
var results = [];
searchAttributesRecursive(yourJsObject, "itemprop", results);
...populates results with nodes which contain the "itemprop" attribute. You can now print the attribute values with a simple loop:
for(var i = 0, len = results.length; i < len; ++i){
console.log(i, results[i].attributes.itemprop);
}
You can do this by using the .some() function. What this does is it will return true if any iteration returns true, otherwise it returns false. So, for every key in the current object, you will check if the property is === 'attributes', and if so, you will check the itemprop property for the desired value. If the current key is not 'attributes', and is === 'children' it will recurse and check each child object in the same way.
var searchAttributesRecursive = function(obj, valueWanted) {
if (obj instanceof Object) {
if (obj.attributes && obj.attributes.itemprop === valueWanted) {
return true;
}
if (obj.children) {
return obj.children.some(function(_obj) {
return searchAttributesRecursive(_obj, valueWanted);
});
} else {
return false;
}
} else {
return false;
}
};
var obj = {
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue0"
},
children: [{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue1"
},
children: []
},
{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue2"
},
children: [{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue3"
},
children: []
}]
}
]
};
console.log("Found 'wantedValue0': " + searchAttributesRecursive(obj, "wantedValue0"));
console.log("Found 'wantedValue1': " + searchAttributesRecursive(obj, "wantedValue1"));
console.log("Found 'wantedValue2': " + searchAttributesRecursive(obj, "wantedValue2"));
console.log("Found 'wantedValue3': " + searchAttributesRecursive(obj, "wantedValue3"));
console.log("Found 'wantedValue4': " + searchAttributesRecursive(obj, "wantedValue4"));
EDIT - To make this work dynamically and search for itemprop === wantedValue in any nested property or nested child property, you can do the following:
var searchAttributesRecursive2 = function(data, valueWanted) {
if (Array.isArray(data)) {
return data.some(function(elem) {
return searchAttributesRecursive2(elem, valueWanted);
});
} else if (data instanceof Object) {
return Object.keys(data).some(function(key) {
var prop = data[key];
return prop.itemprop === valueWanted || searchAttributesRecursive2(prop, valueWanted);
});
} else {
return false;
}
};
var obj = {
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue0"
},
children: [{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue1"
},
children: []
},
{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue2"
},
children: [{
type: 'x',
tagName: 'y',
attributes: {
"itemprop": "wantedValue3"
},
children: []
}]
}
]
};
console.log("Found 'wantedValue0': " + searchAttributesRecursive2(obj, "wantedValue0"));
console.log("Found 'wantedValue1': " + searchAttributesRecursive2(obj, "wantedValue1"));
console.log("Found 'wantedValue2': " + searchAttributesRecursive2(obj, "wantedValue2"));
console.log("Found 'wantedValue3': " + searchAttributesRecursive2(obj, "wantedValue3"));
console.log("Found 'wantedValue4': " + searchAttributesRecursive2(obj, "wantedValue4"));
Related
Traverse through a JSON object which has nested arrays objects inside it .
The label value is provided which is the identifier with which need to return the associated level metrics value . If the label is found in the 2nd level find the metrics at the second level and it should be returned
I couldn't get the logic on how to traverse through an object and return the specific value
function getMetrics(arr, label) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].label === label) {
return arr[i].metricsValue;
} else if (arr[i].children) {
return getMetrics(arr[i].children, label);
}
}
return "Not found";
}
const selectedMetrics = getMetrics(dataObj.series, '1');
Consider the JSON object with children specifies the sub level of the current level .
const dataObj = {
series: [
{
label: "A",
metricsValue: "ma",
children: [
{
label: "A-B",
value: 6,
metricsValue: "ma-mb"
},
{
label: "A-B-C",
metricsValue: "ma-mb-mc",
children: [
{
label : "A-B-C-D",
value: 6,
metricsValue: "ma-mb-mc-md"
}
]
}
]
},
{
label: "1",
metricsValue: "m1",
}
]
};
Expected Result :
When the input is "1", it should return
selectedMetrics= "m1"
Input : "A-B-C-D"
selectedMetrics= "ma-mb-mc-md"
You can perform a Depth first search (DFS) or Breadth first search (BFS) to find metricValues at any level.
Here I'm using DFS to find the required value. This works for data with any nested levels.
const dataObj = { series: [ { label: "A", metricsValue: "ma", children: [ { label: "A-B", value: 6, metricsValue: "ma-mb" }, { label: "A-B-C", metricsValue: "ma-mb-mc", children: [ { label: "A-B-C-D", value: 6, metricsValue: "ma-mb-mc-md" } ] } ] }, { label: "1", metricsValue: "m1"} ] };
function getMetrics(arr, label) {
var result;
for (let i = 0; i < arr.length; i++) {
if (arr[i].label === label) {
return arr[i].metricsValue;
} else if (arr[i].children) {
result = getMetrics(arr[i].children, label);
if (result) {
return result;
}
}
}
return null;
}
console.log("selectedMetrics for 'A' = " + getMetrics(dataObj.series, 'A'));
console.log("selectedMetrics for 'A-B' = " + getMetrics(dataObj.series, 'A-B'));
console.log("selectedMetrics for 'A-B-C' = " + getMetrics(dataObj.series, 'A-B-C'));
console.log("selectedMetrics for 'A-B-C-D' = " + getMetrics(dataObj.series, 'A-B-C-D'));
console.log("selectedMetrics for '1' = " + getMetrics(dataObj.series, '1'));
Your'e passing in the value, so use it instead of the string & you're not accessing the children nodes.
for(var i=0; i< arr.length;i++){
const x = arr[i];
if (x.children.label === value) {
console.log(x.metricValue)
}else{
x.forEach(element => {
if (element.children.label === value) {
console.log(element.metricValue)
}else{
element.forEach(secondEl =>{
if (secondEl.children.label === value) {
console.log(secondEl.metricValue)
}
})
}
});
}
}
You can create a more elegant way of iterating around the children nodes but that may help you out
What I'm trying to solve is: preserving the order of my array of Ids with $in using this suggested method (mapReduce):
Does MongoDB's $in clause guarantee order
I've done my homework, and saw it's ideal to convert them to strings:
Comparing mongoose _id and strings.
Code:
var dataIds = [ '57a1152a4d124a4d1ad12d80',
'57a115304d124a4d1ad12d81',
'5795316dabfaa62383341a79',
'5795315aabfaa62383341a76',
'57a114d64d124a4d1ad12d7f',
'57953165abfaa62383341a78' ];
CollectionSchema.statics.all = function() {
var obj = {};
//adds dataIds to obj.scope as inputs , to be accessed in obj.map
obj.scope = {'inputs': dataIds};
obj.map = function() {
//used toString method as suggested in other SO answer, but still get -1 for Id.
var order = inputs.indexOf(this._id.toString());
emit(order, {
doc : this
});
};
obj.reduce = function() {};
obj.out = {inline: 1};
obj.query = {"_id": {"$in": dataIds } };
obj.finalize = function(key, value) {
return value;
};
return Product
.mapReduce(obj)
.then(function(products){
console.log('map products : ', products)
})
};
This is what I keep getting back for my console.log in the products promise :
[{ _id: -1, value: null } ]
Which, leads me to believe it's not able to match the ObjectId from this, with an index of dataIds. However, if I just use the $in clause within a .find(), the correct products are returned -- but, in the incorrect order.
Update:
getting unexpected behavior with this.
obj.map = function() {
for(var i = 0; i < inputs.length; i++){
if(inputs[i] == this._id.toString()){
}
emit(inputs[i], this);
}
};
emits:
[ { _id: '5795315aabfaa62383341a76', value: null },
{ _id: '57953165abfaa62383341a78', value: null },
{ _id: '5795316dabfaa62383341a79', value: null },
{ _id: '57a114d64d124a4d1ad12d7f', value: null },
{ _id: '57a1152a4d124a4d1ad12d80', value: null },
{ _id: '57a115304d124a4d1ad12d81', value: null } ]
obj.map = function() {
for(var i = 0; i < inputs.length; i++){
if(inputs[i] == this._id.toString()){
var order = i;
}
emit(this._id.toString(), this);
}
};
emits:
[ { _id: 'ObjectId("5795315aabfaa62383341a76")', value: null },
{ _id: 'ObjectId("57953165abfaa62383341a78")', value: null },
{ _id: 'ObjectId("5795316dabfaa62383341a79")', value: null },
{ _id: 'ObjectId("57a114d64d124a4d1ad12d7f")', value: null },
{ _id: 'ObjectId("57a1152a4d124a4d1ad12d80")', value: null },
{ _id: 'ObjectId("57a115304d124a4d1ad12d81")', value: null } ]
Now, how do I get rid of the ObjectId() wrapper? Preferably, something more clean than str.slice(), which would work -- However, I feel there must be a more "mongo" / safer way of converting the Id.
I checked out the docs, but it only mentions the toString() method, which does not seem to be working correctly within map: https://docs.mongodb.com/manual/reference/method/ObjectId.toString/
figured it out:
obj.map = function() {
for(var i = 0; i < inputs.length; i++){
if(this._id.equals(inputs[i])) {
var order = i;
}
}
emit(order, {doc: this});
};
I have the following 2 arrays here:
> chNameArr
[ 'chanel1',
'chanel2',
'chanel3',
'chanel4',
'chanel5',
'chanel6',
'chanel7' ]
and here:
> a
[ 'channelName'
'status',
'connections',
'socketIds',
'lastRun',
'numberOfRuns',
'timeout' ]
what I am trying to achieve is the following objects per channel in an array where channelName from a get the value from chNameArr but the rest of 'a' gets an empty string
file=[{"channelName":"chanel1","status":"", "connections":"", "socketIds":"", "lastRun":"", "numberOfRuns":"", "timeout":""},
.
.
.
{"channelName":"chanel7","status":"", "connections":"", "socketIds":"", "lastRun":"", "numberOfRuns":"", "timeout":""}]
this is my attempt
> chNameArr.map(function(d){return {channelName:d}})
[ { channelName: 'chanel1' },
{ channelName: 'chanel2' },
{ channelName: 'chanel3' },
{ channelName: 'chanel4' },
{ channelName: 'chanel5' },
{ channelName: 'chanel6' },
{ channelName: 'chanel7' } ]
chNameArr.map(function(d) {
result = {};
result[a[0]] = d;
for (var i=1; i<a.length; i++) {
result[a[i]] = "";
}
return result;
})
There is no one-liner to solve this problem in general, although if you don't actually need to use the array a you could manually construct {channelName:d, status: "", ...} in your original map.
Can javascript implement pass-by-reference techniques on function call? You see, I have the JSON below and I need to traverse all its node. While traversing, if the current item is an Object and contains key nodes, I must add another property isParent: true to that exact same item. But I'm having difficulty on creating a traversal function with such feature, and I tried to search for traversal functions, but all I found only returns a new JSON object instead of changing the exact JSON that is being processed.
var default_tree = [
{
text: "Applications",
nodes: [
{
text: "Reports Data Entry",
nodes: [
{ text: "Other Banks Remittance Report" },
{ text: "Statement of Payroll Deduction" },
...
]
},
{
text: "Suspense File Maintenance",
nodes: [
{ text: "Banks with Individual Remittances" },
{ text: "Employers / Banks with Employers" },
...
]
}
]
},
{
text: "Unposted Transactions",
nodes: [
{ text: "Unposted Borrower Payments"},
{ text: "Unposted CMP Payments"}
]
},
{ text: "Maintenance" },
{
text: "Reports",
nodes: [
{
text: "Daily Reports",
nodes: [
{
text: "List of Remittance Reports",
nodes: [
{ text: "Banks" },
...
{
text: "Employers-LBP",
nodes: [
{ text: "Employers-Zonal" }
]
},
]
},
...
]
},
...
]
}
]
Considering we have this traversal function:
function traverse(json_object) {
// perform traversal here
}
traverse(default_tree)
After it runs the traverse function, the default_tree's value will remain the same unless we do something like:
default_tree = traverse(default_tree)
Can someone help me create an iterator will really alter the Object being processed while iterating, instead of returning a new Object?
Please check this one
var default_tree = [....] //Array
function traverse(arrDefaultTree){
arrDefaultTree.forEach(function(val,key){
if(val.hasOwnProperty("nodes")){
val.isParent = true;
traverse(val.nodes);
}
})
}
traverse(default_tree);
console.log(default_tree);
Hope this helpful.
With two functions, one that call then self. One find the nodes and the other one loop througgh the arrays.
traverseTree(default_tree);
function traverseTree (tree) {
var i = 0, len = tree.length;
for(;i < len; i++) {
var obj = tree[i];
findNodes(obj);
}
}
function findNodes (obj) {
var keys = Object.keys(obj);
if (keys.indexOf('nodes') > -1) {
obj.isParent = true;
traverseTree(obj.nodes);
}
}
I have the following object and what I would like achieve is to get the index of theme if the name has match with a variable.
for example: I'm making a loop in the views and if my task (something1) variable has matches with the name element than to return the index of object.
By the given example I should have as result 0,
var views = [
{
name: "something1",
type: something1,
columns: something1
},
{
name: "something2",
type: something2,
columns: something2
},
{
name: "something3",
type: something3,
columns: something3
}
];
var task = 'something1';
$.each(views, function(index, value) {
if (value.name = task) {
alert(index);
}
});
You dont really need jQuery for this:
See: http://jsfiddle.net/enNya/2/
var views = [
{
name: "something1",
type: "something1",
columns: "something1"
},
{
name: "something2",
type: "something2",
columns: "something2"
}
];
var task = 'something2';
// Set a var and maintain scope
var i;
// Loop each element of the array
for (i = 0; i < views.length; i++) {
// If the X = Y the stop looping
if (views[i].name == task) {
break;
}
}
// Check if it was not found
i = i == views.length ? false : i;
// Log the result
console.log(i);
It's just a matter of syntax, as lgt said don't forget toseparate elements within your object with commas. Aslo the correct 'equal' operator is '=='.
'value.name=task' would be always true. It means can I affect the content of 'task' into 'value.name'.
Here is your valid js.
Note that in this example you'll get 2 alertbox.. ;)
var views=[
{
name:"something1",
type:'something1',
columns:'something1'
},
{
name:"something1",
type:'something1',
columns:'something1'
},
{
name:"something2",
type:'something2',
columns:'something2',
},
];
var task='something1';
$.each(views, function(index, value) {
if (value.name==task){
alert(index);
}
});
replace something1 variable for 0 and value.name == task (double =)
var views=[{
name:"something1",
type:0,
columns:0
}, {
name:"something1",
type:0,
columns:0
}, {
name:"something2",
type:0,
columns:0
}];
var task='something1';
$.each(views, function(index, value) {
if (value.name==task){
return index;
}
});