I've got a data set simplified to the following
var data = {
foo: 'bar'
children: [
{
foo: 'bar'
children: [
{ foo: 'bar' }, // count
{ foo: 'bar' }, // count
{
foo: 'bar'
children: [
{ foo: 'bar' }, // count
{ foo: 'bar' }, // count
]
},
{ foo: 'bar' }, // count
]
},
{
// etc
}
]
}
There's a lot more of it than that. Any number of objects below nested.
{
foo: 'bar'
children: []
}
I want to be able to calculate the total 'last children' of any 'node' in the structure. So far I have written a quick script that will work it out from the top level using a counter variable scoped outside of the recursing function - but that stop it being re-usable.
var total = 0;
var countLastChildren = function(object) {
if(object.children) {
object.children.forEach(function(el){
countLastChildren(el);
});
} else {
total++;
}
}
countLastChildren(data);
console.log(total);
I can't quite get my head round how to scope the counter inside countLastChildren() to allow it to return a value and make it re-usable by passing in different objects or objects nested within my main structure.
Any ideas? Thanks
You can just have the function return the count:
var countLastChildren = function(object) {
if(object.children) {
var return_val = 0;
object.children.forEach(function(el){
return_val += countLastChildren(el);
});
return return_val;
} else {
return 1;
}
}
console.log(countLastChildren(data));
Some code golf:
function Total(obj) {
return obj.children
? obj.children.map(Total).reduce(function (prev, cur) { return prev + cur; })
: 1;
}
Related
Following Unexpected outcome when modifying an object in a function
I learned that i have to clone the item passed to the function before changing it and returning it, and it worked for the said example, but when i tried it in my code which was a recursive code, it didn't work, here is an example showing this:
As you can see i intend to update the property B if it exists and if it doesn't i want to create a property B and then give it last value, but for some reason this fails !, of course if i create the property B before hand (before calling it recursively), i can give the value to it, but i don't know why this is needed or why my current code doesn't work!
function addB(item) {
let newItem = { ...item };
if (newItem.B) {
newItem.B.value = "I am B";
} else {
newItem.B = {
value: "I am B"
};
}
if (newItem.children) {
newItem.children.forEach(child => {
//if you uncomment the code below, the code works!
//child.B = {};
child = addB(child);
});
}
return newItem;
}
function App() {
let parent = {
id: 0,
children: [
{
id: 1,
children: [
{
id: 3
},
{
id: 4
}
]
},
{
id: 2
}
]
};
parent = addB(parent);
console.log(parent);
}
Current output:
Expected output:
You can see this example and its result in this CodeSandBox
I think you can do the following:
function addB(item) {
const newItem = { ...item, B: { value: 'I am B' } };
if (newItem.children) {
newItem.children = newItem.children.map(addB);
}
return newItem;
}
If you want to copy the B property if it exist and only set B.value then you can do:
const newItem = { ...item, B: { ...item.B, value: 'I am B' } };
function addB(item) {
const newItem = { ...item, B: { ...item.B,value: 'I am B' } };
if (newItem.children) {
newItem.children = newItem.children.map(addB);
}
return newItem;
}
console.log(
addB({
children: [
{},
{ children: [{}, { B: { other: 2 } }] },
{ B: { something: 1 } },
],
})
);
Another way to write this is:
var addB = item => ({
...item,
B: { ...item.B, value: 'I am B' },
...(item.children
? { children: item.children.map(addB) }
: undefined),
});
I am building an object from a form that is currently rendered server side. I collect all the check boxes displayed in the image below and I am trying to sort them in a way that all the check boxes under each step (1, 2, 3 etc) is a single object based on the property parentNode.
Currently the document.querySelectorAll('.checkboxes') fetches all the checkboxes in following format.
var newObj = [
{
name: 'one',
parentNode: {
id: 'stepOne'
}
},
{
name: 'two',
parentNode: {
id: 'stepTwo'
}
},
{
name: 'three',
parentNode: {
id: 'stepOne'
}
},
]
The new object should be:
var newObj = {
stepOne: [
{
name: 'one',
parentNode: {
id: 'stepOne'
}
},
{
name: 'three',
parentNode: {
id: 'stepOne'
}
},
],
stepTwo: [
{
name: 'two',
parentNode: {
id: 'stepTwo'
}
},
]
}
Usually I do something like this:
let stepOne = function(step) {
return step.parentNode.getAttribute('id') === 'stepOne';
}
let stepTwo = function(step) {
return step.parentNode.getAttribute('id') === 'stepTwo';
}
let allTheStepOnes = fetchCheckBoxes.filter(stepOne);
But filter doesn't work on dom object and this seems inefficient as well.
Proper way of doing this is a forEach loop and using associative arrays like this:
let newObject = {};
originalObject.forEach((item)=>{
let step = item.parentNode.id
if (newObj[step] === undefined) {
newObj[step] = []
}
newObj[step].push(item)
})
Using reduce we can reduce your current array into the new structure.
return newObj.reduce(function(acc, item) {
If acc[item.parentNode.id] has been defined before, retrieve this. Otherwise set it to an empty array:
acc[item.parentNode.id] = (acc[item.parentNode.id] || [])
Add the item to the array and then return it:
acc[item.parentNode.id].push(item);
return acc;
We set the accumulator as {} to start with.
Snippet to show the workings.
var newObj = [{
name: 'one',
parentNode: {
id: 'stepOne'
}
}, {
name: 'two',
parentNode: {
id: 'stepTwo'
}
}, {
name: 'three',
parentNode: {
id: 'stepOne'
}
}, ];
var newOrder = function(prevList) {
return prevList.reduce(function(acc, item) {
acc[item.parentNode.id] = (acc[item.parentNode.id] || [])
acc[item.parentNode.id].push(item);
return acc;
}, {});
}
console.log(newOrder(newObj));
This function should do the trick
function mapObj(obj) {
var result = {};
for(var i = 0; i < obj.length; i++) {
var e = obj[i];
result[e.parentNode.id] = result[e.parentNode.id] || [];
result[e.parentNode.id].push(e);
}
return result;
}
I get an array of objects from a MongoDB through API.
I then need to filter the result furthermore (client side).
I'll work with long lists (could be some thousand of results), each object has about 10 properties with some arrays in it.
Example of an object:
{
_id: xxxxxxx,
foo: [
{ a: "b", c: "d" },
{ a: "b", c: "d" }
],
data: {
a: "b",
c: "d"
}
}
I loop the array async to improve speed:
async.filter(documents, function(value) {
// Search inside the object to check if it contains the given "value"
}, function(results) {
// Will do something with the result array
});
How can I search inside the current object to check if it contains the given value without know in which property I'll find the value?
Though I've not included the async part but I believe overall searching approach could be like this:
// Input Array
var inpArr = [{
id: 1,
foo: [{
a: "dog",
b: "cat"
}]
}, {
id: 2,
foo: [{
a: "kutta",
b: "billi"
}]
}];
var myFilter = function(val, item, index, array) {
var searchResult = scanProperties(item, val);
return searchResult;
};
// Note: pass additional argument to default filter.
// using Function.Prototype.Bind
var filterResult = inpArr.filter(myFilter.bind(null, "dog"));
alert(filterResult);
console.log(filterResult);
// Recursively scan all properties
function scanProperties(obj, val) {
var result = false;
for (var property in obj) {
if (obj.hasOwnProperty(property) && obj[property] != null) {
if (obj[property].constructor == Object) {
result = result || scanProperties(obj[property], val);
} else if (obj[property].constructor == Array) {
for (var i = 0; i < obj[property].length; i++) {
result = result || scanProperties(obj[property][i], val);
}
} else {
result = result || (obj[property] == val);
}
}
}
return result;
};
JS Fiddle Searching an Array of Objects
You can simply iterate through each and every item recursively, like this
var data = {
_id: 1243,
foo: [{
a: "b",
c: "d"
}, {
a: "b",
c: "d"
}],
data: {
a: "b",
c: "d"
}
};
function findValue(value) {
function findItems(document) {
var type = Object.prototype.toString.call(document);
if (type.indexOf("Array") + 1) {
return document.some(findItems);
} else if (type.indexOf("Object") + 1) {
return Object.keys(document).some(function(key) {
return findItems(document[key]);
});
} else {
return document === value;
}
}
return findItems;
}
console.log(findValue('dd')(data));
# false
console.log(findValue('d')(data));
# true
I get the data from database like this:
var json = [
{
name: "one",
roles: [
{ role: "admin",state: 1 },
{ role: "operator",state: 1 },
{ role: "admin",state: 1 }
]
},
{
name: "two",
roles: [
{ role: "admin2",state: 0 },
{ role: "operator",state: 1 },
{ role: "admin",state: 1 }
]
}
];
And I want to become this
=>
var json = [
{
name: "one",
roles:[...],
data: [
{ "admin": 2,"eable": 2,"disable":0 },
{ "operator": 1,"eable": 1,"disable":0}
]
},
{
name: "two",
roles:[...],
data: [
{ "admin": 1,"eable": 0,"disable":1 },
{ "admin2": 1,"eable": 1,"disable":0},
{ "operator": 1,"eable": 1,"disable":0}
]
}
];
I'm getting stuck now, don't know what to do,please help.
Here is what I tried:
json.forEach(function(v,k){
var ret = {
"enable":0,
"disable":0
}
json[k]['data'] = [];
json[k]['roles'].forEach(function(v,k){
json[k]['data'].push( v['role'] );
})
});
http://jsfiddle.net/B9XkX/1/
This is a lot to chew on because the data structure is weird but bear with me:
result = json.map(function(obj){
// we can use map here to transform each object, whatever we return will
// go into the result array.
var roles = obj.roles.reduce(function(memo, item){
// we need to turn a role object into a data object
// but because we are counting the number of eable
// and disable states we need to make a discreet object
// that can hold the numbers without worrying about the
// final data structure.
memo[item.role] = memo[item.role] || {};
memo[item.role].eable = memo[item.role].eable || 0;
memo[item.role].disable = memo[item.role].disable || 0;
// we initialize the memo object if we haven't seen the
// role before.
if (item.state === 1) {
// we count eable for one state
memo[item.role].eable += 1;
} else if (item.state === 0) {
// we count disable for the other state
memo[item.role].disable += 1;
}
return memo;
}, {});
// now the roles object looks something like this:
/**
* {
* admin: {eable: 2, disable: 0},
* operator: {eable: 1, disable: 0}
* }
**/
return {
name: obj.name,
roles: obj.roles,
data: Object.keys(roles).map(function(key){
// now we need to turn the roles object back into an array, so we use
// Object.keys method to turn the keys on the roles object into an array
// and we use map again to setup an object that we will use instead.
var item = {};
item[key] = 1; // {admin: 1}
item.eable = roles[key].eable; // {admin:1, eable: 2}
item.disable = roles[key].disable; // {admin:1, eable: 2, disable: 0}
return item;
})
}
});
I am looking for a technique to run over a object of nested properties and wish to join the properties'.
This is the object I'd like to join:
var array = {
prop1: {
foo: function() {
// Your code here
}
},
prop2: {
bar1: 'some value',
bar2: 'some other value'
}
};
The result should look like this:
[
[ 'prop1', 'foo' ],
[ 'prop2', 'bar1' ],
[ 'prop2', 'bar2' ]
]
Then I'd like to join the array to strings formatted like this:
prop1.foo
prop2.bar1
prop2.bar2
Any tips?
EDIT: Forgot to say it should work for deeper arrays too.
Something along these lines? http://jsfiddle.net/X2X2b/
var array = {
prop1: {
foo: function() {
// Your code here
}
},
prop2: {
bar1: 'some value',
bar2: 'some other value'
}
};
var newA = [],
newB = [];
for ( var obj in array ) {
for (var inObj in array[obj]) {
newA.push([obj, inObj]);
newB.push(obj + '.' + inObj);
}
}
console.log(newA);
console.log(newB);
This is quite a different problem now that you have specified that it needs to support arbitrary depths. In order to solve it we need to use recursion and we need to use a second recursive parameter which keeps track of where we are in the nested hierarchy.
function objectPropertiesToArrays(obj, prepend) {
// result will store the final list of arrays
var result = [];
// test to see if this is a valid object (code defensively)
if(obj != null && obj.constructor === Object) {
for (var propertyName in obj) {
var property = obj[propertyName],
// clone prepend instantiate a new array
list = (prepend || []).slice(0);
// add the property name to the list
list.push(propertyName);
// if it isn't a nested object, we're done
if (property.constructor !== Object) {
result.push(list);
// if it is a nested object, recurse
} else {
// recurse and append the resulting arrays to our list
result = result.concat(objectPropertiesToArrays(property, list));
}
}
}
return result;
}
Example:
var obj = {
prop1: {
foo: function() { }
},
prop2: {
bar1: 'some value',
bar2: 'some other value'
},
prop3: {
x: {
y: [],
z: 'test'
},
erg: 'yar'
}
};
objectPropertiesToArrays(obj);
Returns
[
["prop1", "foo"],
["prop2", "bar1"],
["prop2", "bar2"],
["prop3", "x", "y"],
["prop3", "x", "z"],
["prop3", "erg"]
]