ColdFusion RandRange() vs. Java Collections Shuffle()

Posted July 5, 2007 at 5:28 PM

Tags: ColdFusion

Sometimes, I get the feeling that ColdFusion's RandRange() function just isn't working properly. This of course is not based on anything scientific. In fact, it's mostly based on the fact that manually refreshing a page doesn't render a seemingly random value (like I said, not scientific). So, I wanted to try and compare it to Java's Collections' Shuffle() method, to see if one of the results was very different than the other.

To test this, I am creating an array of values generated by RandRange() and an array of values generated by Shuffle(). Then I am averaging both arrays as well as counting values to see if they both come out to be centered with an even distribution of values:

 Launch code in new window » Download code as text file »

  • <!---
  • Create an array to hold the numbers selected via
  • the ColdFusion method RandRange().
  • --->
  • <cfset arrRandRange = ArrayNew( 1 ) />
  •  
  • <!---
  • Create an array to hold the numbers selected from
  • the first array or a randomly shuffed array (shuffed
  • via the Java.util.Collections library.
  • --->
  • <cfset arrShuffle = ArrayNew( 1 ) />
  •  
  •  
  • <!--- Set the number of iterations we are going to do. --->
  • <cfset intCount = 1000 />
  •  
  •  
  • <!---
  • Loop over the RandRange() funciton a bunch of
  • times to populate the array.
  • --->
  • <cfloop
  • index="intI"
  • from="1"
  • to="#intCount#"
  • step="1">
  •  
  • <!---
  • Use RandRange() to pick a random number
  • between 1 and 3.
  • --->
  • <cfset ArrayAppend(
  • arrRandRange,
  • RandRange( 1, 3 )
  • ) />
  •  
  • </cfloop>
  •  
  •  
  •  
  • <!---
  • Now, let create an array that we are going to
  • shuffle before each random selection.
  • --->
  • <cfset arrForShuffle = ListToArray( "1,2,3" ) />
  •  
  • <!--- Create a collection utility object. --->
  • <cfset objCollections = CreateObject(
  • "java",
  • "java.util.Collections"
  • ) />
  •  
  • <!---
  • Loop over the shuffle function a bunch of
  • times to populate the array. Before we select
  • the number, we are going to shuffle the array
  • from which we are selecting.
  • --->
  • <cfloop
  • index="intI"
  • from="1"
  • to="#intCount#"
  • step="1">
  •  
  • <!--- Shuffle the selection array. --->
  • <cfset objCollections.Shuffle(
  • arrForShuffle
  • ) />
  •  
  • <!---
  • When populating the value array, just select
  • the first value in the shuffled array.
  • --->
  • <cfset ArrayAppend(
  • arrShuffle,
  • arrForShuffle[ 1 ]
  • ) />
  •  
  • </cfloop>
  •  
  •  
  •  
  • <!---
  • Now that we have populated both arrays, let's get
  • the average of all the values. Theoretically, they
  • should both be just about 2 since the randomness
  • should be somewhat evenly distributed.
  • --->
  • RandRange: #ArrayAvg( arrRandRange )#
  • Shuffle: #ArrayAvg( arrShuffle )#

As you can see, I am adding RandRange() values to one array. Then, I am shuffling another array and selecting the first value from it to populate the Shuffle() array. Both loops randomly select the values 1,2, or 3. Running the above code, the numbers always come out to be roughly the same:

RandRange: 2.016
Shuffle: 1.987

RandRange: 1.992
Shuffle: 1.992

RandRange: 1.987
Shuffle: 1.986

RandRange: 1.957
Shuffle: 1.997

But, what about the value distribution? 1000 2s could look the same as 500 1s and 3s. To make sure the counts were evenly distributed, I created an ArrayCount() method that takes an array and a value and returns the number of times that (simple) value can be found in the array:

 Launch code in new window » Download code as text file »

  • <cffunction
  • name="ArrayCount"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Returns the number of times the given value can be found in the given array.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Array"
  • type="array"
  • required="true"
  • hint="The array we will be searching."
  • />
  •  
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The value we will be counting."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  • <!--- Start the default value count. --->
  • <cfset LOCAL.Count = 0 />
  •  
  •  
  • <!--- Loop over the array looking for the value. --->
  • <cfloop
  • index="LOCAL.Index"
  • from="1"
  • to="#ArrayLen( ARGUMENTS.Array )#"
  • step="1">
  •  
  • <!---
  • Check to see if the current index contains
  • a matching value for what we are searching.
  • --->
  • <cfif (ARGUMENTS.Array[ LOCAL.Index ] EQ ARGUMENTS.Value )>
  •  
  • <!--- We found a matching value. --->
  • <cfset LOCAL.Count = (LOCAL.Count + 1) />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Return the value count. --->
  • <cfreturn LOCAL.Count />
  • </cffunction>
  •  
  •  
  •  
  • RandRange
  • ArrayCount( 1 ): #ArrayCount( arrRandRange, 1 )#
  • ArrayCount( 2 ): #ArrayCount( arrRandRange, 2 )#
  • ArrayCount( 3 ): #ArrayCount( arrRandRange, 3 )#
  •  
  •  
  • Shuffle
  • ArrayCount( 1 ): #ArrayCount( arrShuffle, 1 )#
  • ArrayCount( 2 ): #ArrayCount( arrShuffle, 2 )#
  • ArrayCount( 3 ): #ArrayCount( arrShuffle, 3 )#

Again, running the above code shows a very even distribution of values:

RandRange
ArrayCount( 1 ): 316
ArrayCount( 2 ): 317
ArrayCount( 3 ): 367

Shuffle
ArrayCount( 1 ): 316
ArrayCount( 2 ): 343
ArrayCount( 3 ): 341

RandRange
ArrayCount( 1 ): 360
ArrayCount( 2 ): 321
ArrayCount( 3 ): 319

Shuffle
ArrayCount( 1 ): 320
ArrayCount( 2 ): 356
ArrayCount( 3 ): 324

RandRange
ArrayCount( 1 ): 323
ArrayCount( 2 ): 328
ArrayCount( 3 ): 349

Shuffle
ArrayCount( 1 ): 335
ArrayCount( 2 ): 327
ArrayCount( 3 ): 338

For everything above, I was testing on 1000 iterations. I thought maybe that large set is just easy to even out on. But, when I changed from 1000 iterations down to 10 iterations, I found exactly the same average and random number distribution. So, I guess the lesson learned here is that RandRange() is selecting numbers randomly and I am just paranoid :)

Download Code Snippet ZIP File

Comments (8)  |  Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page




ColdFusion Jobs - Find or Post A ColdFusion Job Through DeveloperCircuit.com

Reader Comments

I'm more of a visual person, so I'd prefer to see the data as ordered pairs on a chart to test randomness.

<cfset statsAry = arrayNew(1) />
<cfloop from="1" to="30" index="i">
<cfset arrayAppend(statsAry,randRange(1,10) & ',' & randRange(1,10)) />
</cfloop>
<cfset arraySort(statsAry,'numeric') />
<cfchart format="png" name="statsGraph">
<cfchartseries type="scatter">
<cfloop from="1" to="#arrayLen(statsAry)#" index="i">
<cfchartdata item="#listFirst(statsAry[i])#" value="#listLast(statsAry[i])#">
</cfloop>
</cfchartseries>
</cfchart>
<cfheader name="Content-Type" value="image/png">
<cfheader name="Content-Disposition" value="inline; filename=statsGraph.png">
<cfcontent variable="#statsGraph#">

Posted by Dustin on Jul 6, 2007 at 10:57 AM


Hrmm... there went my tabs, oh well. Anyway, small bug in that code:

This:
<cfchartseries type="scatter">
<cfloop from="1" to="#arrayLen(statsAry)#" index="i">
<cfchartdata item="#listFirst(statsAry[i])#" value="#listLast(statsAry[i])#">
</cfloop>
</cfchartseries>

Should be:
<cfloop from="1" to="#arrayLen(statsAry)#" index="i">
<cfchartseries type="scatter">
<cfchartdata item="#listFirst(statsAry[i])#" value="#listLast(statsAry[i])#">
</cfchartseries>
</cfloop>

Posted by Dustin on Jul 6, 2007 at 11:05 AM


@Dustin,

Cool, I will give this a go at lunch and post of the picture. Thanks.

Posted by Ben Nadel on Jul 6, 2007 at 11:09 AM


Either I'm retarded or I must have not had enough coffee this morning (I'd prefer to think the latter).

For some reason sorting the array as 'numeric' works for having the ordered pairs w/ randRange <10, but not more. Not sure why thats the case, but when I change my values to >10 and (sometimes) it tosses an error on random elements (go figure) saying it was string. Obviously the array was completely filled with strings before too, but why no error? Any ideas?

Fixed code:
<cfset statsAry = arrayNew(1) />
<cfloop from="1" to="30" index="i">
<cfset arrayAppend(statsAry,randRange(1,100)) />
</cfloop>
<cfset arraySort(statsAry,'numeric') />
<cfloop from="1" to="30" index="i">
<cfset statsAry[i] = statsAry[i] & ',' & randRange(1,100) />
</cfloop>
<cfchart format="png" name="statsGraph">
<cfloop from="1" to="#arrayLen(statsAry)#" index="i">
<cfchartseries type="scatter">
<cfchartdata item="#listFirst(statsAry[i])#" value="#listLast(statsAry[i])#">
</cfchartseries>
</cfloop>
</cfchart>
<cfheader name="Content-Type" value="image/png">
<cfheader name="Content-Disposition" value="inline; filename=statsGraph.png">
<cfcontent variable="#statsGraph#">

Posted by Dustin on Jul 6, 2007 at 11:26 AM


Ben,

I have often felt the same way. So, we are both paranoid!

Running your tests in Scribble pad in CFE was good salve for my paranoia though.

Thanks,

Chris P

Posted by Chris Phillips on Jul 7, 2007 at 1:55 PM


@Chris,

I think part of the problem is that there is really no such thing as a truly random number. As such, the number needs to be generated based on some sort of algorithm and seed value (I think). I had thought hat perhaps it was using the internal clock to help generate those values, which might explain the refresh-page technique not showing more seemingly random values.... but I don't really understand how random number generation works, so I don't know how any of it is tied together.

Another interesting experiment would be to see how time affects a random number. Maybe a pattern could be found based on milliseconds of the internal clock.

Of course, I really have no idea what I am talking about :)

Posted by Ben Nadel on Jul 8, 2007 at 12:32 PM


If you don't already, you should try runing the Randomize function just before you run any of Coldfusion's random number generators.

http://livedocs.adobe.com/coldfusion/6/CFML_Reference/functions-pt260.htm

Posted by Steve Savage on Jan 23, 2008 at 9:20 PM


@Steve,

Interesting. I don't think I have used this before. I believe that there is something similar in Javascript that I have used, but never in ColdFusion. I wonder if this affects the outcome of RandRange(), which is the randomization function that I use most often. I can't imagine that RandRange() isn't just sitting on top of the built-in randomization methods.

Posted by Ben Nadel on Jan 24, 2008 at 8:44 AM


Post Comment  |  Ask Ben


Home   |   Web Log   |   ColdFusion   |   Projects   |   Resume   |   Job Form   |   Search   |   Contact
Epicenter Consulting - Custom Software Solutions for Business Evolution HostMySite.com - The Leader In ColdFusion Hosting