I've written the following function to return the average of all the numbers of an array at a given key in an object:
var obj = {
key: [1, 2, 3]
};
function getAverageOfElementsAtProperty(obj, key) {
if (!(key in obj)) {
return 0;
} else if (obj[key].length === 0) {
return 0;
} else {
var total = 0;
for (i = 0; i < obj[key].length; i++) {
total += obj[key][i];
}
return total/(obj[key].length);
}
}
console.log(getAverageOfElementsAtProperty(obj, "key"));
Everything is fine, except I need to add one more else if statement before the else statement that checks if the value at that key is an array. If it isn't, it should return 0. I have looked up ways to do this but none of them have worked.
With instanceof:
var obj = {
k1: [1, 2, 3],
k2: null,
k3: "string",
k4: 5
};
function getAverageOfElementsAtProperty(obj, key) {
if (!(obj[key] instanceof Array) || obj[key].length == 0) {
return 0;
} else {
var total = 0;
for (i = 0; i < obj[key].length; i++) {
total += obj[key][i];
}
return total / (obj[key].length);
}
}
console.log(getAverageOfElementsAtProperty(obj, "k1"))
console.log(getAverageOfElementsAtProperty(obj, "k2"))
console.log(getAverageOfElementsAtProperty(obj, "k3"))
console.log(getAverageOfElementsAtProperty(obj, "k4"))
console.log(getAverageOfElementsAtProperty(obj, "k5"))
To check if a particular property is an array or not, use 'instanceof' operator.
In your case, add the below condition.
else if(! (obj.key instanceof Array)) {
return 0
}
When you say that you've looked up ways but they haven't worked, what have you tried and what problems did you find?
There is the Array.isArray function, which will return a boolean.
Array.isArray([]); // true
You can use Array.isArray(obj[key])
var obj = {
key: [1, 2, 3]
};
function getAverageOfElementsAtProperty(obj, key) {
if(!(key in obj)){
return 0;
}
if(!Array.isArray(obj[key])){
return 0;
}
if(obj[key].length === 0){
return 0;
}
return obj[key].reduce(function(op1, op2){
return op1 + op2;
}, 0)/obj[key].length
}
Related
I have a potential stack overflow issue with a recursive function. Usually I can solve this with a while loop and a condition, but I cannot figure out the condition to based this while loop on.
Here is the current recursive function which counts the number of handlers in an object of unknown # of nested objects.
countHandlers(obj){
let count = 0;
for(let k in obj){
if(k === "_handlers"){
count += obj[k].length;
}
else if(typeof obj[k] === 'object') {
count += this.countHandlers(obj[k])
}
}
return count;
}
Can this be converted to a non-recursive function?
The way I usually get around recursive functions is to use a stack or a queue to maintain the data that needs to be processed.
Stacks are easier in JavaScript, so we'll go with that. :)
function countHandlers(obj) {
let stack = [];
stack.push(obj);
let count = 0;
while (stack.length > 0) {
let currentObj = stack.pop();
for (let k in currentObj) {
if (k === "_handlers") {
count += currentObj[k].length;
}
else if (typeof currentObj[k] === 'object') {
stack.push(currentObj[k]);
}
}
}
return count;
}
Problem arises in such recursive function when you have circular reference. You have to keep track of what objects you already parsed.
Let's say we have this object :
var test = {
_handlers: {
length: 1
},
child1: {
member1: {
_handlers: [7, 9, 12],
child: {
morehandlers: {
_handlers: {
length: 7
}
},
_handlers: [1]
}
},
member2: {
_handlers: {
length: 1
}
}
},
child2: {
value: 2
},
child3: {
last: {
_handlers: {
length: 7
}
}
}
}
Total handlers count should be 20.
And then we add a circular reference :
test.child1.member3 = test;
Here is how I would handle it without thinking of performances :
let parsedHandlers = null;
let handlersCountLaunched = false;
function countHandlers(obj) { // Cannot be async
let countObj = obj;
let count = 0;
for (let i = 0; i < parsedHandlers.length; i++) {
if (countObj === parsedHandlers[i]) {
countObj = null;
break;
}
}
if (countObj !== null) {
parsedHandlers.push(countObj);
for (let k in obj) {
if (k === "_handlers") {
count += obj[k].length;
} else if (typeof obj[k] === 'object') {
count += this.countHandlers(obj[k]);
}
}
}
return count;
}
function getHandlersCount(mainObj) {
if (!handlersCountLaunched) {
parsedHandlers = [];
handlersCountLaunched = true;
let count = countHandlers(mainObj);
handlersCountLaunched = false;
parsedHandlers = null;
return count;
} else {
console.error('TimingError : getHandlersCount() has been called and has not yet finished counting');
return -1;
}
}
console.log(getHandlersCount(test));
In javascript, unless you have setup a mapping logic, you can't retrive the parent object of a member. With a circular reference in the object, you will probably end up with the total amount of handlers in the object tree, unless you select a branch with no circular reference.
Working on a function that verifies if each number in an array is true or false and returns the first true number.
Have a working solution with a for loop as follows:
function findElement(arr, func) {
var num;
for (var a = 0; a < arr.length; a++) {
if (func(arr[a])) {
num = arr[a];
return num;
}
}
return num;
}
findElement([1, 3, 5, 8, 9, 10], function(num) {
return num % 2 === 0;
})
// Should return 8
But I'm trying (in order to get my head around forEach better) to convert it into a forEach loop.
This is where I am so far, but I don't see how to actually return the num out of the loop after it's been established that the function result is true:
function findElement(arr, func) {
if (arr.forEach(func) === true) {
return num;
}
}
findElement([1, 2, 3, 4], num => num % 2 === 0);
Not sure how to use forEach but you could use array.prototype.filter:
function findElement(arr, func) {
return arr.filter(func)[0];
}
#cdhowie suggested the use array.prototype.find in order to need the usage of [0].
function findElement(arr, func) {
return arr.find(func);
}
Obviously, that is just a guess because it may raise an error if no item in the array meets the requirements of func.
If you still are looking about use forEach maybe you could do something like this:
function findElement(arr, func) {
matches = []
arr.forEach((item) => {
if (func(item)) {
matches.push(item)
}
});
return matches[0];
}
Or:
function findElement(arr, func) {
match = null
arr.forEach((item) => {
match = (match == null && func(item)) ? item : match
});
return match;
}
Again, you will have to check how to handle the error if no item in the array meets the requirements f your func.
Any of both codes produce
console.log(findElement([1, 3, 5, 8, 9, 10], function(num) { return num % 2 === 0; })))
8
function findElement(arr, func) {
var num;
arr.forEach(function(element) {
if (!num && func(element)) {
num = element;
}
});
return num;
}
For more options you can check this question: how to stop Javascript forEach?
As has already been mentioned, this is not how forEach should be used.
However, you can still get the behavior you want:
function findElement(arr, func) {
var num;
arr.forEach(item =>{
if (func(item)) {
num = item;
// .forEach does not have an early return
// but you can force it to skip elements by removing them
while (true) {
// Remove all elements
var removedItem = arr.shift();
if (removedItem === undefined) {
// All elements removed
break;
}
}
}
return num;
}
This is even mentioned in the documentation
maybe like this
function findElement(arr, func) {
var num = null;
for (var a = 0; a < arr.length && num === null; a++) {
var val = arr[a];
num = func(val) ? val : null;
}
return num;
}
console.log(findElement([1, 3, 5, 8, 9, 10], function(num) {
return num % 2 === 0;
}));
here is your findElement method with foreach
function findElement(arr, func) {
var num = 0;
arr.forEach(function(item){
if (func(item))
return item;
})
return num;
}
Running a test on my code. It should return 0 if the property passed into the function does not exist. But its not returning anything. Did I make a typo?
var obj = {
key: [1, 2, 3]
};
function getAverageOfElementsAtProperty(obj, key) {
if (obj[key].length === 0 || Array.isArray(obj[key]) === false || obj.hasOwnProperty(key) === false) {
return 0;
}
var average = 0;
for (var i = 0; i < obj[key].length; i++) {
average += obj[key][i];
}
average /= obj[key].length;
return average;
}
console.log(getAverageOfElementsAtProperty(obj, 'notKey'));
If you don't pass an array to your function, it fails when it tries to get the length of the object that was passed, so that shouldn't be how you test initially. You just need to see if the property exists and that is done by simply attempting to access the property in an if condition.
Now, if you are going to add tests to see if the property does exist and that it is an array, then you have to add more tests to check the items in the array to see if they are numbers, otherwise trying to get a numerical average will fail.
Since there are really two things to do (check if the property is there and get math average), I would break this into two functions:
var obj1 = {
key: [1, 2, 3]
};
var obj2 = {
key: [1, "test", 3]
};
function getAverageOfElementsAtProperty(obj, key) {
if (obj[key] && Array.isArray(obj[key])) {
// Ok, we have the right property and there is an array there,
// now we have to test that each item in the array is a number
var numbers = obj[key].every(function (currentValue) {
return typeof currentValue === "number";
});
// Return the average if we have all numbers or 0 if not
return numbers ? getAverage(obj[key]) : 0;
} else {
return 0;
}
}
function getAverage(arr){
var average = 0;
// Array.forEach() is much simpler than counting loops
arr.forEach(function(item) {
average += item;
});
return average / arr.length;
}
console.log(getAverageOfElementsAtProperty(obj1, 'notkey')); // 0
console.log(getAverageOfElementsAtProperty(obj1, 'key')); // 2
console.log(getAverageOfElementsAtProperty(obj2, 'key')); // 0 - obj2 does not contain all numbers
Since obj['notKey'] does not exist, it does not return an array. Therefore you cannot do .length of undefined. I would change it to typeof to see if its defined or not.
var obj = {
key: [1, 2, 3]
};
function getAverageOfElementsAtProperty(obj, key) {
if (typeof obj[key] == 'undefined' || Array.isArray(obj[key]) === false || obj.hasOwnProperty(key) === false) {
return 0;
}
var average = 0;
for (var i = 0; i < obj[key].length; i++) {
average += obj[key][i];
}
average /= obj[key].length;
return average;
}
console.log(getAverageOfElementsAtProperty(obj, 'notKey'));
The problem is to try and remove nested arrays:
steamrollArray([1, [2], [3, [[4]]]]); // should return [1, 2, 3, 4]
I have tried this but the recursion is failing when a nested array appears.
function checkElement(el) {
if (Array.isArray(el)) {
if (el.length === 1) {
return checkElement(el[0]);
} else {
for (var i=0; i < el.length; i++){
checkElement(el[i]);
}
}
} else {
return (el);
}
}
function steamrollArray(arr) {
var finalArr = [];
for (var i=0; i < arr.length; i++){
finalArr.push(checkElement(arr[i]));
}
return (finalArr);
}
A proposal for the first part:
You could change the return value to array and use concat instead of push.
function checkElement(el) {
// collect the values of the checked array
var temp = [];
if (Array.isArray(el)) {
if (el.length === 1) {
return checkElement(el[0]);
} else {
for (var i = 0; i < el.length; i++) {
// collect the values
temp = temp.concat(checkElement(el[i]));
}
// return flat values
return temp;
}
} else {
return el;
}
}
// this can be shorten to
function steamrollArray(arr) {
return checkElement(arr);
}
console.log(steamrollArray([1, [2], [3, [[4]]]]));
Part two, a bit shorter:
function steamrollArray(arr) {
return arr.reduce(function flat(r, a) {
return Array.isArray(a) && a.reduce(flat, r) || r.concat(a);
}, []);
}
console.log(steamrollArray([1, [2], [3, [[4]]]]));
You could use reduce:
function flatten( array ){
return array.reduce( function (a, b){
return a.concat( Array.isArray(b) ? flatten(b) : b );
}, [] );
}
I think this would be the funniest way to do this and also it's one line no more. Also it leaves extraction to native code which is much faster than Scripting.
var nestedArray = [1, [2], [3, [[4]]]];
var flatten = nestedArray.toString().split(',').map(Number);
console.log(flatten);
You can use recursion like this:
function flatten(array) {
var flat = []; //The result array
//An IIFE that will perform the recursion,
//is equivalent to: function rec(param) {.....}; rec(param);
(function rec(a) {
//For each element in the array:
//If the element is an array then call the 'rec' function.
//Else, push it to the result array.
//I used the conditional (ternary) operator (condition ? expr1 : expr2 )
for(var i in a) Array.isArray(a[i]) ? rec(a[i]) : flat.push(a[i]);
})(array);//Initiate the recursion with the main array
return flat;//Return the final result
};
var a = [1, [2], [3, [[4]]]];
function flatten(array) {
var flat = [];
(function rec(a) {
for(var i in a) Array.isArray(a[i]) ? rec(a[i]) : flat.push(a[i]);
})(array);
return flat;
};
console.log(flatten(a));
Using a generator function allows you to efficiently iterate through nested array elements without allocating unnecessary memory. If you really need the flattened array itself, use [...iterable] or Array.from(iterable):
function* deepIterate(array) {
for (a of array) Array.isArray(a) ? yield* deepIterate(a) : yield a;
}
// Iterate through flattened array:
for (a of deepIterate([1,[2,[3]]])) console.log(a);
// Flatten array:
var flat = Array.from(deepIterate([1,[2,[3]]]));
console.log(flat);
You can't just return the values, or it wouldn't work when you have arrays of length > 1.
Here's a solution:
function steamrollArray(arr, flattened) {
if (!flattened) flattened = [];
for (var i=0; i < arr.length; i++){
if (Array.isArray(arr[i])) {
steamrollArray(arr[i], flattened);
} else {
flattened.push(arr[i]);
}
}
return flattened;
}
console.log(steamrollArray([1, [2], [3, [[4]]]])); // should return [1, 2, 3, 4]
Try This if it work for you
function steamrollArray(unflatenArr) {
var flatenArr = [];
if (Array.isArray(unflatenArr)) {
for (var i = 0; i < unflatenArr.length; i++)
arrFlat(unflatenArr[i], flatenArr);
}
return flatenArr;
}
function arrFlat(arr, refArr) {
if (Array.isArray(arr)) {
for (var i = 0; i < arr.length; i++) {
arrFlat(arr[i], refArr);
}
}
else {
refArr.push(arr);
}
}
A simpler solution without using any recursion is by using splice method of Arrays. It works for any level of nesting.
function flattenArray(arr){
for(var i=0;i<arr.length;i++){
if(arr[i] instanceof Array){
Array.prototype.splice.apply(arr,[i,1].concat(arr[i]))
i--;
}
}
return arr;
}
Try this:
function steamrollArray(unflatenArr){
return eval("["+(JSON.stringify(unflatenArr).replace(/\[/g,'').replace(/\]/g,''))+"]")
}
steamrollArray([1, [2], [3, [[4]]]]);
I have an array of arrays as follows:
[[3, 4], [1, 2], [3, 4]]
I wish to create a new array of arrays that has no duplicates, and has a count of the number of occurrences of each element in the first array:
[[3,4,2], [1,2,1]]
here is what I have so far:
var alreadyAdded = 0;
dataset.forEach(function(data) {
From = data[0];
To = data[1];
index = 0;
newDataSet.forEach(function(newdata) {
newFrom = newData[0];
newTo = newData[1];
// check if the point we are looking for is already added to the new array
if ((From == newFrom) && (To == newTo)) {
// if it is, increment the count for that pair
var count = newData[2];
var newCount = count + 1;
newDataSet[index] = [newFrom, newTo, newCount];
test = "reached here";
alreadyAdded = 1;
}
index++;
});
// the pair was not already added to the new dataset, add it
if (alreadyAdded == 0) {
newDataSet.push([From, To, 1]);
}
// reset alreadyAdded variable
alreadyAdded = 0;
});
I am very new to Javascript, can someone help explain to me what I'm doing wrong? I'm sure there is a more concise way of doing this, however I wasn't able to find an example in javascript that dealt with duplicate array of arrays.
Depending on how large the dataset is that you're iterating over I'd be cautious of looping over it so many times. You can avoid having to do that by creating an 'index' for each element in the original dataset and then using it to reference the elements in your grouping. This is the approach that I took when I solved the problem. You can see it here on jsfiddle. I used Array.prototype.reduce to create an object literal which contained the grouping of elements from the original dataset. Then I iterated over it's keys to create the final grouping.
var dataSet = [[3,4], [1,2], [3,4]],
grouping = [],
counts,
keys,
current;
counts = dataSet.reduce(function(acc, elem) {
var key = elem[0] + ':' + elem[1];
if (!acc.hasOwnProperty(key)) {
acc[key] = {elem: elem, count: 0}
}
acc[key].count += 1;
return acc;
}, {});
keys = Object.keys(counts);
for (var i = 0, l = keys.length; i < l; i++) {
current = counts[keys[i]];
current.elem.push(current.count);
grouping.push(current.elem);
}
console.log(grouping);
Assuming order of sub array items matters, assuming that your sub arrays could be of variable length and could contain items other than numbers, here is a fairly generic way to approach the problem. Requires ECMA5 compatibility as it stands, but would not be hard to make it work on ECMA3.
Javascript
// Create shortcuts for prototype methods
var toClass = Object.prototype.toString.call.bind(Object.prototype.toString),
aSlice = Array.prototype.slice.call.bind(Array.prototype.slice);
// A generic deepEqual defined by commonjs
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
function deepEqual(a, b) {
if (a === b) {
return true;
}
if (toClass(a) === '[object Date]' && toClass(b) === '[object Date]') {
return a.getTime() === b.getTime();
}
if (toClass(a) === '[object RegExp]' && toClass(b) === '[object RegExp]') {
return a.toString() === b.toString();
}
if (a && typeof a !== 'object' && b && typeof b !== 'object') {
return a == b;
}
if (a.prototype !== b.prototype) {
return false;
}
if (toClass(a) === '[object Arguments]') {
if (toClass(b) !== '[object Arguments]') {
return false;
}
return deepEqual(aSlice(a), aSlice(b));
}
var ka,
kb,
length,
index,
it;
try {
ka = Object.keys(a);
kb = Object.keys(b);
} catch (eDE) {
return false;
}
length = ka.length;
if (length !== kb.length) {
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return false;
}
} else {
return false;
}
} else {
ka.sort();
kb.sort();
for (index = 0; index < length; index += 1) {
if (ka[index] !== kb[index]) {
return false;
}
}
}
for (index = 0; index < length; index += 1) {
it = ka[index];
if (!deepEqual(a[it], b[it])) {
return false;
}
}
return true;
};
// Recursive function for counting arrays as specified
// a must be an array of arrays
// dupsArray is used to keep count when recursing
function countDups(a, dupsArray) {
dupsArray = Array.isArray(dupsArray) ? dupsArray : [];
var copy,
current,
count;
if (a.length) {
copy = a.slice();
current = copy.pop();
count = 1;
copy = copy.filter(function (item) {
var isEqual = deepEqual(current, item);
if (isEqual) {
count += 1;
}
return !isEqual;
});
current.push(count);
dupsArray.push(current);
if (copy.length) {
countDups(copy, dupsArray);
}
}
return dupsArray;
}
var x = [
[3, 4],
[1, 2],
[3, 4]
];
console.log(JSON.stringify(countDups(x)));
Output
[[3,4,2],[1,2,1]]
on jsFiddle
After fixing a typo I tried your solution in the debugger; it works!
Fixed the inner forEach-loop variable name to match case. Also some var-keywords added.
var alreadyAdded = 0;
dataset.forEach(function (data) {
var From = data[0];
var To = data[1];
var index = 0;
newDataSet.forEach(function (newData) {
var newFrom = newData[0];
var newTo = newData[1];
// check if the point we are looking for is already added to the new array
if ((From == newFrom) && (To == newTo)) {
// if it is, increment the count for that pair
var count = newData[2];
var newCount = count + 1;
newDataSet[index] = [newFrom, newTo, newCount];
test = "reached here";
alreadyAdded = 1;
}
index++;
});
// the pair was not already added to the new dataset, add it
if (alreadyAdded == 0) {
newDataSet.push([From, To, 1]);
}
// reset alreadyAdded variable
alreadyAdded = 0;
});
const x = [[3, 4], [1, 2], [3, 4]];
const with_duplicate_count = [
...x
.map(JSON.stringify)
.reduce( (acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map() )
.entries()
].map(([k, v]) => JSON.parse(k).concat(v));
console.log(with_duplicate_count);