Randomly Sorting A ColdFusion List

Posted August 25, 2006 at 12:59 PM by Ben Nadel

Tags: ColdFusion

The other day, Si asked me if I knew how to randomize a given list. This is something that I figured would be really easy, but was actually a bit stumped. I mean, I could figure it out, but none of the ways seemed very easy or short. I checked out CFLib.org, but couldn't find anything (maybe I didn't know how to search for it). So, I came up with four different methods, each of which has different pros and cons.

For each of the methods below, assume that I have this list:

  • <!--- Set up list of girls. --->
  • <cfset lstGirls = RepeatString(
  • "molly,sarah,julie,julia,ashley,annie,",
  • 1000
  • ) />

Notice that I am REPEATING that list via RepeatString() 1000 times. I need to get a very large list so that the speed differences are apparent (6000 items in total).

Method 1: StructSort()

This method leverages the sorting of structures and then the use of their key lists:

  • <!--- Set the struct holder. --->
  • <cfset objRandomGirls = StructNew() />
  •  
  • <!--- Loop over the list of girls. --->
  • <cfloop index="strGirl" list="#lstGirls#" delimiters=",">
  •  
  • <!--- Add the girls to the struct as the key with a random value. --->
  • <cfset objRandomGirls[ strGirl ] = RandRange( 1111, 9999 ) />
  •  
  • </cfloop>
  •  
  • <!--- Get the random list. --->
  • <cfset lstRandomGirls = ArrayToList(
  • StructSort( objRandomGirls, "numeric", "ASC" )
  • ) />

As you can see in this one, each girl is a key in struct. For each girl, I assign a random number, then sort the struct on that number. The resultant array is then converted back into a list. This runs very fast, at about 50ms for 6000 girls. The problem that you very quickly see though, is that it does NOT allow for duplicates. Each repeated girl name is stored at the SAME key in the struct and so, the resultant, random list is only 6 girls long. So fast, but NOT useful (except if maybe you have no repeating values).

Method 2: ListAppend() / ListPrepend()

For this method, I basically take one girl at a time and randomly append or prepend her to the new list.

  • <!--- Set the random list. --->
  • <cfset lstRandomGirls = "" />
  •  
  • <!--- Loop over the list of girls. --->
  • <cfloop index="strGirl" list="#lstGirls#" delimiters=",">
  •  
  • <!--- Decide if we are appending or prepending. --->
  • <cfif RandRange( 0, 1 )>
  •  
  • <!--- Append the girl. --->
  • <cfset lstRandomGirls = ListAppend(
  • lstRandomGirls,
  • strGirl
  • ) />
  •  
  • <cfelse>
  •  
  • <!--- Prepend the girl. --->
  • <cfset lstRandomGirls = ListPrepend(
  • lstRandomGirls,
  • strGirl
  • ) />
  •  
  • </cfif>
  •  
  • </cfloop>

At the end of this (as always) lstRandomGirls will have the random list. This method is both slow (running at about 2000ms for 6000 girls) and not very useful. The randomization is very limited when you can only add an item to a list in two ways.

Method 3: ArraySwap()

In this method, I tried using an array since array access times are much better than list access times. We convert the list to an array and then randomly swap items.

  • <!--- Set the random array. --->
  • <cfset arrGirls = ListToArray( lstGirls ) />
  •  
  • <!--- Get the length of the array. --->
  • <cfset intLength = ArrayLen( arrGirls ) />
  •  
  • <!--- Iterate the number of girls x 2. --->
  • <cfloop index="intIndex" from="1" to="#(intLength * 2)#" step="1">
  •  
  • <cfset ArraySwap(
  • arrGirls,
  • RandRange( 1, intLength ),
  • RandRange( 1, intLength )
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- Get the random list. --->
  • <cfset lstRandomGirls = ArrayToList( arrGirls ) />

If you will notice, I randomly swap items a total of (N x 2) times where N is the length of the list. I decided to go x2 times just to increase the random factor. This method is actually fairly fast running in anywhere from 50ms to 230ms per 6000 girls. It actually creates a fairly random list as well, despite the fact that method is not very clever.

Method 4: ListInsertAt()

In this method, I am reading the given list and then randomly inserting it into the randomized list.

  • <!--- Set the random list. --->
  • <cfset lstRandomGirls = "" />
  •  
  • <!--- Set the counter. --->
  • <cfset intCounter = 0 />
  •  
  • <!--- Loop over the list of girls. --->
  • <cfloop index="strGirl" list="#lstGirls#" delimiters=",">
  •  
  • <!--- Check to see which iteration we are on. --->
  • <cfif intCounter>
  •  
  • <!--- Insert that girl in a random place. --->
  • <cfset lstRandomGirls = ListInsertAt(
  • lstRandomGirls,
  • RandRange( 1, intCounter ),
  • strGirl
  • ) />
  •  
  • <cfelse>
  •  
  • <!--- Start the list. --->
  • <cfset lstRandomGirls = strGirl />
  •  
  • </cfif>
  •  
  • <!--- Increment the counter. --->
  • <cfset intCounter = (intCounter + 1) />
  •  
  • </cfloop>

This method is sooo slow that is started to timeout the page (even when my timeout was 900 seconds) for 6000 girls. Not only is it slow, I hate the fact that ListInsertAt() does NOT work for an empty list - hence the need for an initial setting of the random string.

So there are my four methods, two of which actually might be useful. Right now, I am leaning towards the ArraySort() one as I am picturing a user defined function (UDF) that, as an optional argument, can take the number of swapping iterations to make. However I do it though, I still with there was a simpler, faster answer.

Note: I have totally missed a built in way of doing this, PLEASE let me know :)



Reader Comments

Mar 19, 2008 at 4:15 PM // reply »
28 Comments

Ben, this is a really interesting article. It gave me some good inspiration for a UDF that returns a random list for a query, array, or struct:
http://www.mollerus.net/tom/blog/2008/03/creating_randomlyordered_lists.html


Jul 15, 2009 at 11:35 PM // reply »
8 Comments

The first way works quite well if your list is really a set not a list. That way your struct will always be the same size as your set.
I use this when presenting a random subset of a query. I use a value list of the primary keys to build the list.


Sam
Mar 9, 2010 at 1:21 AM // reply »
1 Comments

just found this.... thank you.


Jun 5, 2011 at 2:56 PM // reply »
14 Comments

Check this out. Ray Camden wrote it back in 2002

<cfloop condition="listlen(mylist)">
<cfset target = randRange(1,listLen(myList))>
<cfoutput>#listGetAt(mylist,target)#<br></cfoutput>
<cfset myList = listDeleteAt(myList,target)>
</cfloop>

A rather cool, nice simple solution


Jun 5, 2011 at 5:02 PM // reply »
11,314 Comments

@Chris,

Thanks for the code - Ray was definitely way ahead of the game. Right now, my approach of choice for sorting lists (arrays for that matter) is to use the Collections.shuffle() approach that Mark Mandel showed me:

http://www.bennadel.com/blog/280-Randomly-Sort-A-ColdFusion-Array-Updated-Thanks-Mark-Mandel.htm


Jun 6, 2011 at 5:12 AM // reply »
14 Comments

@Ben

Thank you so much for the link. For my immediate requirement, this was an even simpler solution


Jun 4, 2013 at 9:09 AM // reply »
1 Comments

Ben, you just saved my ass. I tried doing this with a bunch of other snippets. jQuery, cflib... nothing seemed to work with CF10. That is until I found this. :) Thanks!


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 18, 2013 at 3:39 PM
Experimenting With The Amazon Simple Storage Service (S3) API Using ColdFusion
Hi Ben, THANKS! While not bleeding edge, it is new to me & I like learning new things every day! ... read »
Jun 18, 2013 at 12:30 PM
Disabling Auto-Correct And Auto-Capitalize Features On iPhone Inputs
Also spellcheck="false" should be mentioned as part of html5 specs ... read »
Jun 18, 2013 at 8:40 AM
Using Named Functions Within Self-Executing Function Blocks In Javascript
Hi Ben, you forgot to mention the most important thing for named self-executing functions - they can be referenced by name ONLY inside their execution context (which is parens in this case), it mean ... read »
dee
Jun 18, 2013 at 7:01 AM
My Safari Browser SQLite Database Hello World Example
hai ben, this program is really good i could understand the concept but i dint know how to save it and how to open it as you have done in the video can u give that details pls ... read »
Jun 18, 2013 at 6:04 AM
Clearing Inline CSS Properties With jQuery
Thanks a lot for for post! It helped me a lot... after being stuck since 24 hrs.. found solution from your post. Thanks again! ... read »
Jun 18, 2013 at 2:31 AM
SOTR 2013 - The Best Conference I Never Went To
I keep watching it, should keep me happily distracted until SotR14 ;) ... read »
Jun 17, 2013 at 9:45 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, As I was reading what you wrote, it occurred to me that maybe I do something similar to that in some of my client-side code. In an application I'm working on, there are a bunch of unrelated ... read »
Jun 17, 2013 at 9:36 PM
Object Thinking By David West
@Jonah, Please, don't feel bad at all. I appreciate all that you have contributed to the conversation. And, the more points of view I get, the more confident I am that I will some day, some how und ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools