ColdFusion 101: Picking a random image (2)

After my post yesterday on selecting or rotating images, a reader asked if it was possible to show a random image once and not again until the other images are shown. That is certainly possible, and here is one way to do it...

<cfapplication name="img" sessionManagement="true">

<!--- Get full path to images. ---> <cfset imageDirectory = expandPath(".")>

<!--- Get directory ---> <cfdirectory action="list" directory="#imageDirectory#" name="images" filter="*.jpg">

<!--- Do we have any images? ---> <cfif images.recordCount gt 0>

<!--- store ID values ---> <cfif not structKeyExists(session, "totalList") or session.totalList is ""> <cfset session.totalList = valueList(images.name)> </cfif>

<!--- pick a random number ---> <cfset pickedIndex = randRange(1, listLen(session.totalList))>

<!--- pick from list ---> <cfset image = listGetAt(session.totalList, pickedIndex)>

<!--- remove from total list ---> <cfset session.totalList = listDeleteAt(session.totalList, pickedIndex)>

<!--- display it ---> <cfoutput><img src="#image#"></cfoutput>

</cfif>

The way I handled it was to simply store the list of filenames in a session variable. This then becomes my list of data to randomly select from. Because the list gets smaller on every hit, I have to check for either the session variable not existing, or if it is empty, and if so, I fill in the values.

Running this version will give you a random order of images, with no image repeated until they have all been shown. There is one exception to this. It is possible that if dharma.jpg, for example, was picked last, that it could then be picked first on reload. As homework, modify the code above to handle that edge case.

p.s. As a reminder, do not forget that you can subscribe to the blog by using the Subscribe form on the right hand side. This will let you get entries via email.

Archived Comments

Comment 1 by todd posted on 8/16/2006 at 4:44 PM

would this work? i know people don't like using cfbreak, but it seems like it would work. i'm probably missing a much easier solution here.

<cfapplication name="img" sessionManagement="true">

<!--- Get full path to images. --->
<cfset imageDirectory = expandPath(".")>

<!--- Get directory --->
<cfdirectory action="list" directory="#imageDirectory#" name="images" filter="*.jpg">

<!--- Do we have any images? --->
<cfif images.recordCount gt 0>

<!--- store ID values --->
<cfif not structKeyExists(session, "totalList") or session.totalList is "">
<cfset session.totalList = valueList(images.name)>
</cfif>

<!--- pick a random number --->
<cfset pickedIndex = randRange(1, listLen(session.totalList))>

<!--- if the random number is equal to the last index and we're at the beginning of the list --->
<cfif structKeyExists(application.lastIndex) and application.lastIndex eq pickedIndex and listLen(session.totalList) eq images.recordCount>
<cfloop from="1" to="#listLen(session.totalList)#" index="i">
<cfset pickedIndex = randRange(1, listLen(session.totalList))>
<cfif pickedIndex neq application.lastIndex>
<cfbreak>
</cfif>
</cfloop>
</cfif>

<!--- pick from list --->
<cfset image = listGetAt(session.totalList, pickedIndex)>

<!--- remove from total list --->
<cfset session.totalList = listDeleteAt(session.totalList, pickedIndex)>

<!--- set an application variable containing the last index in the list so we don't repeat --->
<cfif listLen(session.totalList) eq 1>
<cfset application.lastIndex = pickedIndex>
</cfif>

<!--- display it --->
<cfoutput><img src="#image#"></cfoutput>

</cfif>

Comment 2 by todd posted on 8/16/2006 at 4:47 PM

or a conditional loop?

<cfloop condition="pickedIndex neq application.lastIndex">
<cfset pickedIndex = randRange(1,listLen(session.totalList))>
</cfloop>

Comment 3 by todd posted on 8/16/2006 at 5:06 PM

should be session.lastIndex, not application.

Comment 4 by db posted on 8/16/2006 at 8:04 PM

if the image name was stored in the client scope, then would it also prevent repeating across sessions?

<!--- Get full path to images. --->
<cfset imageDirectory = "#rootpath#img">

<!--- Get directory --->
<cfdirectory action="list" directory="#imageDirectory#" name="images" filter="*.jpg">

<!--- Do we have any images? --->
<cfif images.recordCount gt 0>

<!--- store ID values --->
<cfif not structKeyExists(session, "totalList") or session.totalList is "">
<cfset session.totalList = valueList(images.name)>
</cfif>

<!--- remove last picked from the list --->
<cfif structKeyExists(client, "imagepicked")>

<cfset session.totalList = listDeleteAt(session.totalList, listfind(session.totalList,client.imagepicked))>

<!--- at end of list, repopulate excluding last picked --->
<cfif session.totalList is "">
<cfset session.totalList = listDeleteAt(valueList(images.name), listfind(valueList(images.name),client.imagepicked))>
</cfif>

</cfif>

<!--- pick a random number --->
<cfset pickedIndex = randRange(1, listLen(session.totalList))>

<!--- pick from list --->
<cfset client.imagepicked = listGetAt(session.totalList, pickedIndex)>

<!--- display it --->
<cfoutput><img src="#basehref#img/#client.imagepicked#"></cfoutput>

</cfif>

Comment 5 by Tony Petruzzi posted on 8/16/2006 at 11:34 PM

Yet another way using only a cookie as the holder and QoQ to filter the images already shown to the user. Yes I know that cookies have a limit of 4K for the domain, but this is just an example.

<!--- Get full path to images. --->
<cfset imageDirectory = expandPath("../images")>

<!--- Get directory --->
<cfdirectory action="list" directory="#imageDirectory#" name="images" filter="*.jpg">

<!--- Do we have any images? --->
<cfif images.recordCount gt 0>

<!--- create a cookie if we don't have one OR reset the cookie if we've shown all the images' --->
<cfif not structKeyExists(cookie, "imageList") OR ListLen(cookie.imageList, "|") EQ images.recordCount>
<cfcookie name="imageList" expires="never" value="">
</cfif>

<!--- remove images already shown --->
<cfquery name="imagesQuery" dbtype="query">
SELECT name FROM images
<cfif cookie.imageList NEQ "">
WHERE
1 = 1
<cfloop list="#cookie.imageList#" delimiters="|" index="i">
AND name <> <cfqueryparam value="#i#">
</cfloop>
</cfif>
</cfquery>

<!--- only if we have left over images --->
<cfif imagesQuery.RecordCount GT 0>

<!--- pick the lucky image --->
<cfset imagePicked = imagesQuery.name[RandRange(1, imagesQuery.RecordCount)]>

<!--- append to the cookie --->
<cfset cookie.imageList = ListAppend(cookie.imageList, imagePicked, "|")>

<!--- display it --->
<cfoutput><img src="../images/#imagePicked#"></cfoutput>

</cfif>

</cfif>

Comment 6 by Joe posted on 9/6/2006 at 7:25 PM

How about using this same code and implementing a linkable image?

Comment 7 by Raymond Camden posted on 9/6/2006 at 7:29 PM

Joe, what do you mean? You mean linking to the image instead of showing it? That's just an HTML change and would be trivial.

Comment 8 by Joe posted on 9/6/2006 at 9:38 PM

Ray,
I just want to attach a link to the image. I have specific links for each image. Ex. I click on Home Depot's image to get to Home Depot's website.

Comment 9 by Raymond Camden posted on 9/7/2006 at 1:51 AM

Well, that is getting more complex. Our current code is based on physical files, but if you want other info along with the images, you most likely want to switch to a database approach.

Comment 10 by Kevin posted on 12/7/2007 at 8:49 PM

Picking a two random numbers from a list, the list may not be repeated suppose
1 student has 10 books ( 1, 2,3,4,5,6,7,8,9 ,10)
2 student has 9 ( 11,22,13,14,15,16,17,18,19)
3 student has 7 ( 20,21,23,24,24,25,26).All this books are in list if I were to pick two random books for each student. How can that be done. The example above deletes the picked number, here I need to delete the list for eg 1 studnet, start from 2nd student.