JavaScript challenge: Create an average-calculating function using recursion? - javascript

I've had trouble writing this code. I'm supposed to make a function that can take either an array of numbers or the arguments array and calculate the average without using a for or while loop. It says I have to use recursion. How do I do this?

I was able to finish it myself thanks to what you guys suggested. I was confused on how to go about the actual average calculation until after reading what you guys posted. If this code can be improved please tell! Thanks!
function mean( list, more ) {
if ( more ) {
list = [].slice.call( arguments );
} else if ( !list || list[0] === undefined ) return;
var a = list,
b = list.length;
return (function execute() {
if ( !a.length ) return 0;
return ( a.pop() / b ) + execute();
})();
}

I assume you're familiar with recursion.
Just implement a recursive function with an index argument to keep track of where you are, and add the numbers to the same variable. Then at the end, divide by the size of your array.
Edit:
As Kranklin points out in a comment, using pop you won't even need the index argument. (You will need to store the size of the array before iterating).

Here it is but by looking at it you agree to understand it:
http://jsfiddle.net/sparebyte/kGg9Y/1/
function calcAverage(nums, total, count) {
if(isNaN(count)) {
// First iteration: Init Params
return calcAverage(nums, 0, nums.length);
}
if(nums.length) {
// Middle itrations: Get a total
total = nums.pop() + total;
return calcAverage(nums, total, count)
} else {
// Last iteration: Find the total average
return total / count
}
};

function average(set, memo, total) {
memo || (memo = 0);
total || (total = set.length);
if (set.length === 0) return memo / total;
return average(set.slice(1, set.length), (memo + set[0]), total);
}
You call it like this:
average([1,2,3,4]); // 2.5

Here's what I came up with:
function av(nums, i, t) {
if (!Array.isArray(nums))
return av([].slice.call(arguments));
if (t === void 0){
if (nums.length === 0)
return;
return av(nums, nums.length-1, 0);
}
t += nums[i];
if (i > 0)
return av(nums, i-1, t);
return t / nums.length;
}
Accepts an array of numbers, or if the first param is not an array assumes all arguments are numbers. (No error checking for non-numeric data like av('a','x').) Returns undefined if array is empty or no params are supplied.
alert( av([1,2,3,4]) ); // 2.5
alert( av(1,2,3,4,5) ); // 3
Assumes Array.isArray() (or an appropriate shim) is available.

Related

recursion on returning vectors c++

Hey guys I am trying trying to right this javascript code into c++. I am doing quick sort and everything is straight forward minus the last step.
function quickSort(arr)
{
//base case if the arr is 1 or 0 then return the array
if(arr.length === 1 || arr.length === 0)
{
return arr;
}
var pivotIndex = Math.floor(arr.length/2);
var pivotValue = arr[pivotIndex];
var before = [];
var after = [];
for(var counter = 0; counter < arr.length; counter++)
{
if(counter === pivotIndex)
continue;
if(pivotValue <= arr[counter])
{
before.push(arr[counter])
}
else
{
after.push(arr[counter])
}
}
//this step I am having trouble rewriting in c++
return quickSort(after).concat(pivotValue).concat(quickSort(before));
}
I am having a hard time rewriting the recursive step in c++. I am not sure how concat 2 vector. I tried using the insert method but I keep getting an error about invalid use of void expression.
vector<int> quickSort(vector<int> arr)
{
if(arr.size() == 1 || arr.size() == 0)
{
return arr;
}
int pivotIndex = arr.size()/2;
int pivotValue = arr[pivotIndex];
vector<int> before;
vector<int> after;
//put values in before or after the piv
for(size_t counter = 0; counter < arr.size(); counter++)
{
if(counter == pivotIndex)
continue;
if(pivotValue <= arr[counter])
before.push_back( arr[counter]);
else
after.push_back( arr[counter]);
}
return //????? not sure how to do this
}
So, you realized that your core question was "how to concatenate two vectors", and you found a right answer: using insert. Now your question is about why you were getting "an error about invalid use of void expression." (That's the assumption my answer is for, at least.)
That's because you were likely trying to do something like the following:
return quickSort(after).insert( /* stuff */ );
which is wrong. In JavaScript, array.concat returns the concatenated array. It's return type is effectively Array, and so doing return arr.concat(arr2) returns an Array because arr.concat would return an Array. Further, in JavaScript, array.concat doesn't modify the array it was called on, but rather returns a new array.
In C++, however, vector.insert (#4 in the reference) returns void. That means it returns nothing. So when you try to return the result of insert, you get that error about invalid use of a void expression. Further, in C++, vector.insert does modify the vector it was called on.
So how do you use insert in this case?
vector<int> quickSort(vector<int> arr)
{
// ...
// Sort `before` and `after`
before = quickSort(before);
after = quickSort(after);
// Modify `after` and return it.
after.push_back(pivotValue);
after.insert(after.end(), before.begin(), before.end());
return after;
}
Note: My code isn't optimal and the idea of rewriting JS in C++ is also oddly specific. My answer is to simply outline the problem asked in the question, not to give a good C++ implementation of quick sort.
To concat two vector , you can use std::merge
like:std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(dst));

How to insert a string in an array with a specific order

So I have the following array :
let array = ['9h00','9h30','9h45','10h00','10h15']
Let's say I want to insert '9h15' between '9h00' && '9h30'
How would one do it in a fast & efficient solution ?
EDIT: I have been unclear, the index of the insert would change depending on the array. It won't always be the following position : array[1].
Thanks in advance!
If every item has the same format (\d?\d)h(\d{2}) then we can do the following :
First we need a function to convert the string to an object or something we can work with, I'll go with an object :
function timeStringToObj(str){
var re = "^(\d?\d)h(\d\d)$";
var h = str.replace(re, "$1");
var m = str.replace(re, "$2");
return {
hours: parseInt(h),
minutes: parseInt(m)
};
}
Then we will need to ensure that the array is sorted, for instance "9h15" < "9h16" therefore "9h15"'s index is < "9h16"'s index , (if not use array.sort(/*some sorting function if necessary*/)) and loop through it to find the spot (I'll use a function of course), I'll consider your array as a global variable :
/**
*#param timeObjA an object that is the return value of a call of timeStringToObj
*#param timeObjB an object that is the return value of a call of timeStringToObj
*/
function AgeB(timeObjA, timeObjB){//A greater than or equal to B
return (timeObjA.hours >= timeObjB.hours && timeObjA.minutes >= timeObjB.minutes);
}
/**
*#param a an object that is the return value of a call of timeStringToObj
*#param b an object that is the return value of a call of timeStringToObj
*/
function AleB(a, b){//A less than or equal to B
return (timeObjA.hours <= timeObjB.hours && timeObjA.minutes <= timeObjB.minutes);
}
function putInTimeArray(str){
var val = timeStringToObj(str);
for(let i = 0 ; i < array.length ; i+=1){
var curr = timeStringToObj(array[i]);
//first check : elem >= last
if( AgeB(val, curr) && i===(array.length-1) ){
array.push(str);
return true;
}
//second check : elem <= first
if( AleB(val, curr) && i===0 ){
array.unshift(str);
return true;
}
//last check : first < elem < last
if(i!=0 && i!=(array.length - 1)){
if(AgeB(val, curr)){
array.splice(i+1,0,str);
return true;
}
if(AleB(val, curr){
array.splice(i-1,0,str);//here i-1 is safe since i!=0 (condition)
return true;
}
}//end of last check
}//end of for-loop
}//end of function
If you're having doubts regarding my usage of splice please refer to this : How to insert an item into an array at a specific index?
EDIT
You'll probably need a more sophisticated regex to be more appropriate be this will do the job just fine if you don't go that crazy with those strings
You can push and sort like:
array.push("9h15").sort().reverse(); // ["9h45", "9h30", "9h15", "9h00", "10h15", "10h00"]

Why is the first argument of reduce() returning undefined?

I'm trying to write a function that takes in an array as an input. If the integer is positive, it counts it. If the integer is negative, it sums it.
I figured that that the reduce() helper in js would be the best way to go about this, but I keep returning undefined for my first argument when it runs.
Here's my code:
function countPositivesSumNegatives(input) {
let countPositive = 0;
let sumNegative = 0
if (input === null || input === []){
return [];
} else {
return input.reduce(function(prev,num){
if (num > 0) {
countPositive++;
}else{
sumNegative = prev + num};
}, 0);
}
return [countPositive, sumNegative];
}
It throws me a TypeError that says:
TypeError: Cannot read property '0' of undefined
When I log 'prev' to the console inside of the reduce function, it logs undefined for all inputs except the first one. The first one, as expected, is 0. But for each following input it logs undefined. Why is this happening?
Thanks in advance.
The callback you pass to .reduce() needs to return the cumulative value (the value that will be passed as prev to the next iteration of the loop. Since you are returning nothing, you get undefined for the next iteration of your loop.
This complicates what you're trying to do because you are trying to keep track of two values in your loop. As such, you would either have to avoid using prev at all or you'd have to make it be a data structure that had both your values in it. Your use is not a textbook example for .reduce(). Your code is probably simpler with an iteration using .forEach() or for/of.
function countPositivesSumNegatives(input) {
let countPositive = 0;
let sumNegative = 0
if (!input || input.length === 0){
return [];
} else {
input.forEach(function(num){
if (num > 0) {
++countPositive;
} else {
sumNegative += num;
});
}
return [countPositive, sumNegative];
}
Well sorry but this is not a good implementation of this function. But we can correct your function as follows;
function countPositivesSumNegatives(input) {
let countPositive = 0;
let sumNegative = 0;
if (input === null || input === []){
return [];
} else {
sumNegative = input.reduce(function(prev,num){
if (num > 0) {
countPositive++;
} else {
prev += num;
}
return prev; // <---- THE MISSING PART
}, 0);
}
return [countPositive, sumNegative];
}
var data = [1,2,3,4,5,-4,7,-3];
console.log(countPositivesSumNegatives(data));
However while the code works just fine it still involves many issues. When getting into functors like .reduce() you should be able to keep everthing contained within itself and should not refer to variables at the outer scope. Accordingly one can simply rephrase this code as follows;
var data = [1,2,3,4,5,-4,7,-3],
cpsn = a => a.reduce((p,c) => c > 0 ? (p[0]++,p) : (p[1]+=c,p) ,[0,0]);
console.log(cpsn(data))

How to generate a new random number (that's different from the previous random number)

I'm trying to change the following (that currently returns a random number from an array), so that each random number is different from the last one chosen.
function randomize(arr) {
return arr[Math.floor(Math.random()*arr.length)];
}
oracleImg = [];
for (var i=1;i<=6;i++) {
oracleImg.push(i);
}
randOracleImg = randomize(oracleImg);
I tried the following, but it's not always giving me a number different from the last number.
function randomize(arr) {
var arr = Math.floor(Math.random()*arr.length);
if(arr == this.lastSelected) {
randomize();
}
else {
this.lastSelected = arr;
return arr;
}
}
How can I fix this?
Your existing function's recursive randomize() call doesn't make sense because you don't pass it the arr argument and you don't do anything with its return value. That line should be:
return randomize(arr);
...except that by the time it gets to that line you have reassigned arr so that it no longer refers to the original array. Using an additional variable as in the following version should work.
Note that I've also added a test to make sure that if the array has only one element we return that item immediately because in that case it's not possible to select a different item each time. (The function returns undefined if the array is empty.)
function randomize(arr) {
if (arr.length < 2) return arr[0];
var num = Math.floor(Math.random()*arr.length);
if(num == this.lastSelected) {
return randomize(arr);
} else {
this.lastSelected = num;
return arr[num];
}
}
document.querySelector("button").addEventListener("click", function() {
console.log(randomize(["a","b","c","d"]));
});
<button>Test</button>
Note that your original function seemed to be returning a random array index, but the code shown in my answer returns a random array element.
Note also that the way you are calling your function means that within the function this is window - not sure if that's what you intended; it works, but basically lastSelected is a global variable.
Given that I'm not keen on creating global variables needlessly, here's an alternative implementation with no global variables, and without recursion because in my opinion a simple while loop is a more semantic way to implement the concept of "keep trying until x happens":
var randomize = function () {
var lastSelected, num;
return function randomize(arr) {
if (arr.length < 2) return arr[0];
while (lastSelected === (num = Math.floor(Math.random()*arr.length)));
lastSelected = num;
return arr[num];
};
}();
document.querySelector("button").addEventListener("click", function() {
console.log(randomize(["a","b","c","d"]));
});
<button>Test</button>
Below code is just an example, it will generate 99 numbers and all will be unique and random (Range is 0-1000), logic is simple just add random number in a temporary array and compare new random if it is already generated or not.
var tempArray = [];
var i=0;
while (i != 99) {
var random = Math.floor((Math.random() * 999) + 0);
if (tempArray.indexOf(random)==-1) {
tempArray.push(random);
i++;
} else {
continue;
}
}
console.log(tempArray);
here is a version which will ensure a random number that is always different from the last one. additionally you can control the max and min value of the generated random value. defaults are max: 100 and min: 1
var randomize = (function () {
var last;
return function randomize(min, max) {
max = typeof max != 'number' ? 100 : max;
min = typeof min != 'number' ? 1 : min;
var random = Math.floor(Math.random() * (max - min)) + min;
if (random == last) {
return randomize(min, max);
}
last = random;
return random;
};
})();
If you want to ALWAYS return a different number from an array then don't randomize, shuffle instead!*
The simplest fair (truly random) shuffling algorithm is the Fisher-Yates algorithm. Don't make the same mistake Microsoft did and try to abuse .sort() to implement a shuffle. Just implement Fisher-Yates (otherwise known as the Knuth shuffle):
// Fisher-Yates shuffle:
// Note: This function shuffles in-place, if you don't
// want the original array to change then pass a copy
// using [].slice()
function shuffle (theArray) {
var tmp;
for (var i=0; i<theArray.length;i++) {
// Generate random index into the array:
var j = Math.floor(Math.random()*theArray.length);
// Swap current item with random item:
tmp = theArray[i];
theArray[j] = theArray[i];
theArray[i] = tmp;
}
return theArray;
}
So just do:
shuffledOracleImg = shuffle(oracleImg.slice());
var i=0;
randOracleImg = shuffledOracleImg[i++]; // just get the next image
// to get a random image
How you want to handle running out of images is up to you. Media players like iTunes or the music player on iPhones, iPads and iPods give users the option of stop playing or repeat from beginning. Some card game software will reshuffle and start again.
*note: One of my pet-peeves is music player software that randomize instead of shuffle. Randomize is exactly the wrong thing to do because 1. some implementations don't check if the next song is the same as the current song so you get a song played twice (what you seem to want to avoid) and 2. some songs end up NEVER getting played. Shuffling and playing the shuffled playlist from beginning to end avoids both problems. CD player manufacturers got it right. MP3 player developers tend to get it wrong.

Newb: Why does this variable persist instead of getting reset?

I suppose this is a newbie question, but I can't seem to figure it out. I have this code, from eloquent javascript, about the reduce function:
function forEach ( info, func ) {
for ( i = 0; i < info.length; i++) {
func(info[i]);
}
}
function reduce (combine, base, array) {
forEach(array, function(elem) {
base = combine(base, elem);
console.log("The base is: " + base);
});
return base;
}
function countZeroes(array) {
function counter(total, element) {
console.log("The total is: " + total);
return total + (element === 0 ? 1 : 0);
}
return reduce(counter, 0, array);
}
What I can not figure out is, how is the number of zeroes stored in total through each call of the function? Why does it keep a running tab, instead of getting wiped out each time?
The structure of reduce is that it applies a function f which takes two operands - here called element and total to a sequence. element is the next unprocessed item in the sequence (array); total is the result of the previous call to f.
Conceptually reduce(f, 0, [1,2,3]) expands to f(3,f(2,f(1,0).
Now, to answer your question: the running total is stored between invocations of counter in the variable base inside reduce.

Categories