Random Numbers in Javascript
The ability to pick a value at random underlies many of the things we'll want to do with Javascript, allowing us to define objects that behave unpredictably. That makes the simple statement Math.random() one of the most powerful incantations in Javascript's book of spells.
Generating (Pseudo-)Random Numbers
The Math object in Javascript contains a number of methods that perform mathematical operations. Among these is random(), which generates an apparently random number between 0 and 1. To generate a random-number value and assign it to a variable, use a statement like this:
<script type="text/javascript">
someVariable = Math.random();
</script>
Note that the M in Math must always be capitalized. Names of predefined or native objects always use capitals.
Technically speaking the output of Math.random() is only pseudo-random, because the technique used to generate its values will display a detectable pattern if it is repeated enough times. Unless you study hundreds of thousands of repetitions, however, you probably won't notice.
Below are five examples showing Math.random() in action. In each, a script displays randomly generated numbers or words. Reload the present page to see the values change.
Example 1: Simple Math.random()
Here's the code for this example:
<script type="text/javascript">
document.write(Math.random()+">br /<");
</script>
Any time you see multiple statements repeated without change, of course, you should probably substitute a loop. We've left that feature out to minimize complexity.
By themselves, long decimal numbers between 0 and 1 are not very useful. To select from among a range of elements, you'll want to match the output of Math.random() against a series of tests, as in this example:
<script type="text/javascript">
if(randyItem == 0) document.location = "firstCourse.htm";
if(randyItem == 1) document.location = "secondCourse.htm";
if(randyItem == 2) document.location = "thirdCourse.htm";
if(randyItem == 3) document.location = "fourthCourse.htm";
</script>
These four lines change the current Web page to something completely different, depending on the value assigned to randyItem, which is fed by a random-number generator.
As you can see, the if tests here assume we're dealing with integers. Two steps are required to get from the raw output of Math.random() to useful integer values.
First, multiply the output of Math.random() by the number of items from which you wish to choose. In the example above we'd multiple by 4:
randyItem = Math.random()*4;
Here's a modified version of our initial example, multiplying the result of Math.random() by 4:
Example 2: Math.random() multiplied by range
And here's the code:
<script type="text/javascript">
document.write(Math.random()*4+"<br />");
document.write(Math.random()*4+"<br />");
document.write(Math.random()*4+"<br />");
document.write(Math.random()*4+"<br />");
</script>
As you can see, when you multiply an integer like 4 by a messy decimal number like 0.427009978920274, the result is itself a messy decimal (1.708039915681096, to be exact).
We need a simple integer, not a mess. To clean up the results, we apply another method of the Math object called floor(). This method rounds the decimal down to the nearest integer. In the case above, it yields 1. (If that seems odd, remember that Math.floor rounds DOWN in all cases.)
The statement in question looks like this:
randyItem = Math.floor(Math.random()*4);
Yes, you can nest methods, which means submitting the results of one method as the argument to another method. Here, we pass the result of our random-number method, multiplied by 4, to the Math.floor integer-conversion method.
Be careful with those embedded parentheses, though -- you have to get them just right.
I've grown attached to variable names including randy over the years, but these names are entirely arbitrary. You may call your random values anything you want, provided you don't use a name that's already defined in Javascript.
Here are four more iterations of Math.random(), this time multiplied by 4 and rounded down with Math.floor():
Example 3: Math.random() multiplied by range and rounded down
If you study the output above, you'll see that the random values may include 0 but never include 4. That's because we're rounding DOWN. If we wanted to include 4 and exclude 0, we'd use the Math.ceil() function instead of Math.floor(). If we want to use floor, to generate a range from 0 to 4, inclusive, we would use 5 as our multiplier term.
Which function you choose depends on what you want to do with the random value. Suppose you want to extract a randomly-chosen value from a specially constructed object called an Array. At heart, Arrays are simply ordered lists, though their numbering systems differ from everyday practice in one respect: they always start at 0.
Arrays are numbered from 0 to one less than their total length -- from 0 to 3, for an array with four elements. Thus the numbers generated by a random function, then rounded down with Math.floor(), will always correspond to indices of the array, because they will always start with zero and end at one less than the total range.
If we use an Array containing fifteen words, we can now use Math.floor(Math.random()*15)) to generate a random word list:
Example 4: Randomized selection from Array
Constraining random values
If you re-run the example above a few times, you'll notice it sometimes contains duplicate items. These duplicates can even occur one after another. This happens because we're choosing from a relatively small range of options, and because nothing prevents the script from generating the same random integer twice in succession. (Technically speaking, the problem lies with the Math.floor() operation, which takes nearly unique decimals and rounds down to a much smaller range of integers.)
Suppose we want to prevent any value from being chosen twice in succession. To add this constraint, we first need to track random values, so we can compare any value generated on one iteration of the program against the value generated on the previous iteration. Thus along with our random value variable, randy, we use a new variable that we'll call randyOld, which stores a previous value for comparison.
Now things get a little complicated. To eliminate repetitions, we need to check every random number against its predecessor. However, the first time we run the script, there is no predecessor -- unless we assign one. Thus we might begin our script with the following:
randy = 0; randyOld = 0;
We initialize to zero here, but that's an arbitrary choice. As long as both variables are set to the same value, you can use any number you like.
Note that we would make these initial assignments of randy and randyOld at the beginning of our markup, before any code that might refer to them, such as a function. This placement is essential.
Armed with a recording system, we can write a random number generator that screens out repetitions. Expressed in the form of a function, it might go something like this:
function randomOut()
{
randyRange = 10;
randy = Math.floor(Math.random()*randyRange);
if(randy == randyOld)
{
rangeA = randyRange-randy;
rangeB = randy-1
if((rangeA > rangeB) || (rangeA == rangeB))
{
randy = Math.ceil(Math.random()*rangeA)+randy;
}
else{
randy = Math.floor(Math.random()*rangeB);
}
}
randyOld = randy;
}
Given a specified range, this script generates a random number on that range and then checks for repetition. If the value it just generated, randy, matches its previous output, randyOld, then it assigns values to two variables, rangeA and rangeB. The first is the number of values greater than the current random value, while the second is the number of values lower than the current number.
Next we ask which range is larger, or if the two are equal. If one range is larger than the other, we want to select our new random value from the larger range, to maximize possibilities. We do that by adding randy (the current random value) back to the range, then using the ceil or ceiling function instead of floor so that the new numbers selected will always be greater than the current number.
If both ranges are the same size, we arbitrarily choose the range of higher numbers. If the lower range is larger, we use it.
Finally, we assign to the record variable randyOld the new value of randy we just generated. Doing this allows us to test the next random candidate against the value we just found.
A simpler method for constraining randoms
If you think about it, you'll see that the method we just described can be extended to multiple degrees of constraint, by adding more tracking variables. Thus you could write a randomizer that would generate unique choices for two, three, or as many iterations as you like.
However, things get complicated when you introduce so many variables. If you're content to restrict your random constraint to one level -- that is, to rule out ever picking the same number twice in succession -- then there's a much simpler method.
Again, we assume that randy and randyOld have been defined outside this function.
function randomOut()
{
randyRange = 10;
while(randy == randyOld)
{
randy = Math.floor(Math.random()*randyRange);
}
randyOld = randy;
}
Much simpler, no? This method simply says, if the current random value and the previous random value are the same, pick again.
The key to this trick is a control structure called a called a while loop. This kind of loop forces Javascript to repeat its included instructions as long as the governing condition is true. When the condition becomes false, control passes to the next statement outside the loop.
Assuming the two variables are initialized to the same value, the loop's condition will always be met the first time the script runs. Because we set the two variables equal to each other once we've found a non-repeating value, the condition is always met subsequently. Since the loop contains an instruction to generate a fresh random number, it's highly likely to terminate itself.
This method works quite well, especially if you set the range of selection (randyRange in this case) to some reasonably large number.
Technically speaking, though, this method has an important limitation. It's physically possible for the browser to pick the very same number many times in succession. Obviously, the odds diminish as the value of "many" increases. Given 10 possible choices, the odds of picking the same number three times over are not all that low. On the other hand, if you were to roll a pair of dice and come up with snake eyes twenty times in succession, you might want to examine those dice with a hacksaw.
Our simplified constraint method could throw your browser into a prolonged (though not infinite) loop. By the same token, all the atoms in your toenails could spontaneously transmute into gold. Neither event is very likely in the world as we know it. (Too bad about the golden toenails, actually.)
So for demonstration projects and prototypes, the simpler algorithm just described will serve well enough. However: the simpler approach is NOT suitable for large-scale, server-based applications intended for a large number of simultaneous users. If each of a million users experiences a few seconds delay, you could be looking at a significant slowdown in server-based processing.
We're not studying server-side Javascript in this class, so this caution is not acutely relevant; but if you go on to more advanced work, keep it in mind.
|
|