JPass
JPass

Reputation: 33

Using coldfusion, how do I display the elements of an array in random order?

Using ColdFusion, my goal is to output all the elements of an array in a totally random order. My array has 6,000 items which are pulled from an XML file.

Usually, if I didn't require them to be output in a random order, I would do this:

<cfoutput>
<cfloop from="1" to="#arraylen(thestring)#" index="x">
#thestring[x]#<br/>
</cfloop>
</cfoutput>

The result is that my strings are displayed in the order they were when pulled from the XML file:

thestring[1]
thestring[2]
thestring[3]
thestring[4]

Etc. etc. all the way up to thestring[6000].

What I would like to see is all items output in totally random order:

thestring[5]
thestring[22]
thestring[4301]
thestring[201]
thestring[2041]

Upvotes: 1

Views: 4592

Answers (5)

Leigh
Leigh

Reputation: 28873

Update: This was more of a quick demo to demonstrate an earlier suggestion would work . But a few of the other suggestions are more streamlined. So I would recommend using one of them instead.

Based on your latest update, I see no reason Raymond's suggestion would not work. As long as you keep track of the elements already displayed. Here is a simple example

<cfscript>
    // generate sample array
    yourArray = [];
    for (x = 1; x <= 20; x++) {
            arrayAppend(yourArray, "item "& x);
    }

    // stores used indices
    visited = {};
    for (x = 1; x <= arrayLen(yourArray); x++) {

            // get a random index we have not seen before
            do {
                    index = randRange(1, arrayLen(yourArray));
            }
            while (structKeyExists(visited, index));

            // display it
            WriteOutput(yourArray[index] &"<br>");

            // mark are "visited" so we do not display it again
            visited[index] = true;
    }
</cfscript>

Upvotes: 1

Scott Stroz
Scott Stroz

Reputation: 7519

The potential problem with tracking the 'used' items is that there may be many more iterations than needed to output all the items. For example, lets say element 10 was the first item displayed, by tracking it as used, its possible that 10 will come up in randrange() again before all items have been displayed. The more items that are 'used' the higher the likelihood that we will have to add an iteration to get an element that was not used.

One way we can make sure we only make 6000 iterations is to reduce the size of the array with each iteration and do randRange( 1, arrayLen({array name}).

//set up array
test = [];
for( x=1; x<=6000; x++ ){
    arrayAppend( test, "Item " & x );

}

//here is the meat of the process
done = false;
count = 1;
while( !done ){
    index = randRange( 1, arrayLen( test ) );
    writeOutput( count & " : " & test[ index ] & "<br/>" );
    arrayDeleteAt( test, index );
    count++;
    done = !arrayLen( test );
}

The code above was averaging under 100ms to execute on my development machine.

Also, after testing out Henry's code, I noticed that it could be tweaked a little, here is the slightly streamlined code.

<!--- set up the array --->
<cfset theString = []>
<cfloop from="1" to="6000" index="i">
    <cfset arrayAppend(theString, "Item : " & i) />
</cfloop>

<cfset collections = createObject("java","java.util.Collections") />
<cfset collections.shuffle( theString ) />
<cfoutput>

<cfloop array="#theString#" index="x">
    #x#<br/>
</cfloop>
</cfoutput>

Notice I got rid of the arrayresize() and that you can simply pass the original array into collections.shuffle() and then loop over that.

Upvotes: 1

Henry
Henry

Reputation: 32905

based on Non repeating random numbers , Fisher-yates shuffle algorithm is what you want and it has already been implemented in java.util.Collections's shuffle(), see: Java's Collections.shuffle is doing what?

<cfset indices = []>
<cfset arrayResize(indices, 6000)>
<cfloop from="1" to="6000" index="i">
    <cfset arrayAppend(indices, i)>
</cfloop>

<cfset collections = createObject("java","java.util.Collections")>
<cfset collections.shuffle(indices)>

<cfloop array="#indices#" index="x">
    #thestring[x]#<br/>
</cfloop>

Very efficient. 6000 items only takes ~3ms using a quick getTickCount() test on my PC.

Upvotes: 1

Evik James
Evik James

Reputation: 10493

I have used stuff like this many times with great success. I have NEVER tried this on 6,000 items though. This is one way to skin a cat.

<!--- create list of unusused indexes --->
<cfset ListOfUnusedIndexes = "">
<cfset ItemsRemaining = "#arrayLen(YourArray)#">

<!--- populate list from 1 to lenght of array --->
<cfloop from="1" to="#arrayLen(YourArray)#" index="i">
   <cfset ListOfUnusedIndexes = listAppend(ListOfUnusedIndexes , i)>
</cfloop>

<!--- loop through ---->
<cfloop from="1" to="#arrayLen(YourArray)#">
  <!--- pick a random index from 1 to the number of items remaining --->
  <cfset RandomNumber = randRange(1, ItemsRemaining)>
  <!--- get that index from the list of unused numbers --->
  <cfset IndexToGet = listGetAt(ListOfUnusedIndexes, RandomNumber)>
  <!--- output the item from your array --->
  <cfset ArrayItemToOutput = thestring[IndexToGet]>
  <!--- remove the index from your list of unusued indexes --->
  <cfset ListOfUnusedIndexes = listDeleteAt(ListOfUnusedIndexes, IndexToGet)>
  <!--- decrement the number of items remaining --->
  <cfset ItemsRemaining = ItemsRemaining - 1>
</cfloop>

Upvotes: 2

Raymond Camden
Raymond Camden

Reputation: 10857

Loop from 1 to N, with N being the number of times you want to loop, and then simply pick a random element using randRange(1, arraylen(thestring)).

Upvotes: 0

Related Questions