I have written the following program in javascript:
function recursiveSum(a) {
sum = 0;
for (i=0;i<a.length; ++i) {
if (typeof a[i] === "number") {
sum += a[i];
} else if (a[i] instanceof Array) {
sum += recursiveSum(a[i]);
}
}
return sum;
}
function arraySum(a) {
// i will be an array, containing integers, strings and/or arrays like itself.
// Sum all the integers you find, anywhere in the nest of arrays.
return recursiveSum(a);
}
And I can't figure out why the result of arraySum([[1,2,3],4,5]) is 6. Why the elements after the first array are not processed?
You have a problem with Global Variables. You need to use var, it is not optional.
Both sum and i need to be declared with var.
var sum = 0;
for (var i=0;i<a.length; ++i) {
Your sum and i variables are globals, because you haven't declared them as local to the function. You're falling prey to The Horror of Implicit Globals. It's primarily the i variable that's causing the trouble with your specific input: Since your first entry in a is an array, i gets incremented by the recursive call, and the last two entries in the array are never processed by the outer call. (But if you'd used [1, 2, [3, 4, 5]], the fact the calls share both i and sum would be causing trouble.)
Put var in front of each of them. Also look into using the new strict mode, which would have made that a useful error.
Related
I'm following an online course about Javascript Functional Programming
at the Exercise 16 it show you how reduce is actually implemented, in order to help you understand how to use it, but into this implementation there is something i don't actually get, i'll show the code:
Array.prototype.reduce = function(combiner, initialValue) {
var counter, accumulatedValue;
// If the array is empty, do nothing
if (this.length === 0) {
return this;
}
else {
// If the user didn't pass an initial value, use the first item.
if (arguments.length === 1) {
counter = 1;
accumulatedValue = this[0];
}
else if (arguments.length >= 2) {
counter = 0;
accumulatedValue = initialValue;
}
else {
throw "Invalid arguments.";
}
// Loop through the array, feeding the current value and the result of
// the previous computation back into the combiner function until
// we've exhausted the entire array and are left with only one value.
while(counter < this.length) {
accumulatedValue = combiner(accumulatedValue, this[counter])
counter++;
}
return [accumulatedValue];
}
};
I don't understand the first if statement, when it check for this.length what this actually mean?
Take note this is different from the reduce in ES5, which returns an value instead of an Array, this is used just as a sample for the learning purpose.
Array.prototype.reduce = function(...
is saying, "create a function on the prototype of Array" - this means that the new reduce function will be callable on all arrays, eg:
[1, 2, 3].reduce(...
This means you can also call it on empty arrays, eg:
[].reduce(...
Building on the comment:
If the array is empty, do nothing
You're working on an array, and when the function is called, this is set to the array that reduce was called on. This implementation of reduce assumes that if that array is empty (ie this.length === 0), you can't logically reduce it any further - there's nothing to reduce, so you can return the same empty array.
As pointed out by #Alnitak in the comments, this implementation of reduce is flawed as compared to the specification. A different implementation is available on the MDN for polyfilling older browsers.
I am wondering, what is the best approach to write a recursive function with no direct base case (say: factorial), for instance, to count the number of elements in a nested array I have two approaches in mind, the first one below is preferred as it returns result directly:
the second one keeps the count in a variable attached to the function, works fine, but dealing with the result & resetting the variable is bizarre.
any pointers are appreciated.
You can simply return the value you are interested in:
function countElements(arr) {
var count = 0;
for (var i=0; i<arr.length; i++) {
if (arr[i] instanceof Array) {
count += countElements(arr[i]); // recursion here
} else {
count++; // normal element counts as 1
}
}
return count;
}
Demo: http://jsbin.com/ejEmOwEQ/1/edit
WARNING: The function might not end if the array contains self reference (var arr = []; arr.push(arr); countElements(arr);)
The correct way to write this is simply:
function countElements (obj) {
if (obj instanceof Array) {
var count = 0;
for (var i in obj)
count += countElements(obj[i]);
return count;
}
return 1
}
The terminating condition you're looking for is if not instanceof Array. Which in my code above is simply the fall through from the if instanceof Array block.
You do not need to keep a temp variable like count in recursive functions. You're still thinking iteratively (well, that for loop is iterative so you need a count variable there).
Recursive functions do everything by accepting arguments and returning results. No assignments are necessary. In fact, the code above can be written purely recursively without using a for loop and therefore without needing to use a count variable:
function countElements (obj) {
if (obj instanceof Array) {
if (obj.length) {
return countElements(obj.shift()) + countElements(obj);
}
return 0;
}
return 1;
}
There are 3 rules: if object is not an array we return 1, if object is an empty array we return 0 otherwise we count the first item in the array + the sum of the rest of the array.
var avg = function()
{
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++)
{
sum += arguments[i];
}
return sum / arguments.length;
}
When I try to call this like:
var average = avg(2,3,5);
average; // It works fine;
But how do I call it without assigning to a variable?
If anybody can give any suggestion it will be delightful..Thanks.
You'd simply call it like this:
avg(2, 3, 5);
If you want to see the result, put it in an alert call:
alert( avg(2, 3, 5) );
You don't need to put the result from calling the function in a variable, you can do whatever you like with it.
For example, use it as the value in an alert:
alert(avg(2,3,5));
You can use it in another expression:
var message = "The average is " + avg(2,3,5);
You can use it directly in another call:
someFunction(avg(2,3,5));
You can even throw the result away by not doing anything with it, even if that's not useful in this specific situation:
avg(2,3,5);
If you don't put the result into a variable or in a compatible context, this function cannot output anything, which makes it difficult to use unless you make it output the result. Try this :
var avg = function()
{
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++)
{
sum += arguments[i];
}
var retvalue = sum / arguments.length;
consoloe.log("avg: "+retvalue);
return retvalue ;
}
Then it may help you to see whenever the function is called or not.
You need to understand the concept of expressions.
Every expression as a whole represents one value. An expression can be made up of multiple subexpressions that are combined in some manner (for example with operators) to yield a new value.
For instance:
3 is an expression (a literal, to be specific) that denotes the numeric value three.
3 + 4 is an expression, made up of two literal expressions, that as a whole yields the value 7
When you assign a value to a variable – as in var average = – the right hand side of the =-operator needs to be an expression, i.e. something that yields a value.
As you have observed, average will have been assigned the value five. It thus follows, that avg(2, 3, 5) must itself be an expression that evaluated to the value 5.
average itself is an expression, denoting the current value of said variable.
The most important thing to take away from this is, that avg() is in no way connected to var average =. avg() stands on its own, you can just think it like an ordinary value such as 5 (there are other differences of course).
If you have understood the concept of expressions and values, it should be clear that if you can do
var average = avg(2,3,5);
average; // It works fine;
You can also do
avg(2,3,5);
function genEnemy(a) {
//javascript:alert(en[0]+'\n'+genEnemy(en[0])+'\n'+en[0])
with (Math) {
a[1]=round(a[1]*(.5+random()))
a[2]=round(a[2]*(1+random()))
for (var b=0;b<5;b++) a[3][b]=round(a[3][b]*(a[3][b]/2+random()*a[3][b]/10))
for (var b=0;b<a[4].length;b++) random()<it[a[4][b]][3]/10?a[4][b]=0:0
}
return a
}
Script to generate an enemy's stats given the bases each enemy array. (RPG game)
The problem is, when I am expecting it to return an array containing the new stats, it also sets the enemy array to the new one. Why is this? Obviously you can see how problems are caused by this (the bases being changed so a weak enemy can become uber powerful). How would I stop it from setting the array in en (array of enemy values)?
Objects are passed by reference in JavaScript. That means any changes you make to the array a inside genEnemy is reflected on the original array that was passed in. You need to make a deep copy of the array and return this copy. Here is a function that will do it for you:
function cloneArray(a) {
var b = [];
for (var i = 0; i < a.length; i++)
if (a[i] instanceof Array)
b[i] = cloneArray(a[i]);
else
b[i] = a[i];
return b;
}
Inside genEnemy, you would then do:
a = cloneArray(a);
// make changes to the new array
return a;
Also, don't forget to include semicolons in your code. Even though they are optional, you may run into unexpected problems if you get into the habit of omitting them.
Are you passing array en to your getEnemy() function? If so then it will change the values in en array as it's passed by reference not by value
This is so simple I am baffled. I have the following:
var x = 'shrimp';
var stypes = new Array('shrimp', 'crabs', 'oysters', 'fin_fish', 'crawfish', 'alligator');
for (t in stypes) {
if (stypes[t] != x) {
alert(stypes[t]);
}
}
Once the values have iterated it starts returning a dozen functions like
function (iterator, context) {
var index = 0;
iterator = iterator.bind(context);
try {
this._each(function (value) {iterator(value, index++);});
} catch (e) {
if (e != $break) {
throw e;
}
}
return this;
}
What the heck is going on?
Edit: In these scripts I am using http://script.aculo.us/prototype.js and http://script.aculo.us/scriptaculous.js I remember now reading about the way prototype extends arrays and I am betting this is part of it. How do I deal with it?
The for enumeration is going to go over every member of the object you passed it. In this case an array, which happens to have functions as members as well as the elements passed.
You could re-write your for loop to check if typeof stypes[t] == "function" or yada yada. But IMO you are better off just modifying your looping to only elements..
for(var i = 0, t; t = stypes[i]; ++i){
if (t != x) {
alert(t);
}
}
Or
for(var i = 0; i < stypes.length; ++i){
if (stypes[i] != x) {
alert(stypes[i]);
}
}
I wanted to migrate my last comment up to the answer to add the notice of the a caveat for the first type of loop.
from Simon Willison's "A re-introduction to JavaScript"..
for (var i = 0, item; item = a[i]; i++) {
// Do something with item
}
Here we are setting up two variables.
The assignment in the middle part of
the for loop is also tested for
truthfulness - if it succeeds, the
loop continues. Since i is incremented
each time, items from the array will
be assigned to item in sequential
order. The loop stops when a "falsy"
item is found (such as undefined).
Note that this trick should only be
used for arrays which you know do not
contain "falsy" values (arrays of
objects or DOM nodes for example). If
you are iterating over numeric data
that might include a 0 or string data
that might include the empty string
you should use the i, j idiom instead.
you want to do:
for (var i in object) {
if (!object.hasOwnProperty(i))
continue;
... do stuff ...
}
As for..in enumeration iterates over all properties (enumerable or otherwise) that exist on both the object and its prototype chain. The hasOwnProperty check restricts iteration to just those properties on the actual object you want to enumerate.
ES5 makes things a little better for library developers (and help avoid this stuff) but we won't see that ina shipping browser for quite a while :-(
[edit: replacing return with continue. lalalalala ;) ]
Since prototype has extended the array for your convenience you should take advantage of it. Your example could be rewritten as:
var x = 'shrimp';
var stypes = new Array('shrimp', 'crabs', 'oysters', 'fin_fish', 'crawfish', 'alligator');
stypes.without(x).each(alert);
It should be
for (t in stypes) {
if (t != x) {
alert(t);
}
}