CFML 101 - Fun with Lists

{ Posted By : Eric Cobb on December 23, 2009 }
4889 Views
Related Categories: CFML 101

I love lists in CFML. I try to use lists whenever possible. They're efficient, clean, and powerful. I've wished for a long time that SQL Server and Oracle had built in list functions that were as easy to use as the CFML list functions. I'm not going to go over all of the list functions here, but I will cover a few examples and ways they can make your life easier.

Have you ever found yourself with a long cfif statement that's checking the same thing over and over? Something like one of these:

<cfif variables.userID is 1 or variables.userID is 4 or variables.userID is 7 or variables.userID is 10>...</cfif>

<cfif variables.userName is "Bob" or variables.userName is "Betty" or variables.userName is "John" or variables.userName is "Jane">...</cfif>

An easier way would be to use listFindNoCase() or listFind() to compare items. For example:

<cfif ListFind('1,4,7,10', variables.userID)>...</cfif>

<cfif ListFindNoCase('Bob,Betty,John,Jane', variables.userName)>...</cfif>

There, isn't that better? To me that's much easier to read, and according to Adobe's Coding Best Practices for ColdFusion Performance, when it comes to using listFindNoCase() or listFind() instead of the "is" and "or" operators "They are much faster".

List functions can be used on a variety of items, not just comma delimited lists. Since you have the option to specify what your delimiter is, this really opens the door as to what you can do with lists in CFML.

For example, let's say you wanted to separate the dollars and cents from the value $14.95, just specify the dot as your list delimiter.

<cfoutput>
    #ListFirst("$14.95",".")#<br />
    #ListLast("$14.95",".")#
</cfoutput>

Would give you:

$14
95

Or you wanted to find out how many words are in a sentence by specifying a blank space as your list delimiter:

<cfoutput>
    #ListLen("CFML is da bomb!", " ")#
</cfoutput>

Would give you:

4

You could also separate words in a sentence or phrase using a blank space as your list delimiter:

<cfoutput>
    #ListFirst("CFML Rocks!"," ")#<br />
    #ListLast("CFML Rocks!"," ")#
</cfoutput>

Would give you:

CFML
Rocks!

Lists can also have multiple delimiters. To borrow an example from LiveDocs:

<cfset MyList="1,2;3,4;5">
<cfoutput>
    List length using ; and , as delimiters: #listlen(Mylist, ";,")#<br>
    List length using only , as a delimiter: #listlen(Mylist)#<br>
</cfoutput>

Would displays the following output:

List length using ; and , as delimiters: 5
List length using only , as a delimiter: 3

In the multiple delimiter example above either the comma or the semicolon is interpreted as a delimiter when parsing the list, but not the combination of both. Meaning, ";" and "," are each seen as an individual delimiter, not the combined value of ";,".

ListFind() vs. ListContains()

These two functions can be confusing. It's easy to get them mixed up. They both do the same thing, sort of, but they do it just a little bit differently. Here's how I remember which function is which:

ListFind() will find an exact match to the specified value.
ListContains() will return a match that contains the specified value (though it doesn't have to be an exact match).

Here's an example that might explain it a little better:

Take the following list of numbers

<cfset variables.numList = "10,11,12,13,14,15">

If you were search for the number 1 using ListFind() like so:

<cfoutput>
    #ListFind(variables.numList,"1")#
</cfoutput>

you would not find a match. The number 1 is not an individual item in our list, so there is not an exact match. However, if you were to search the same list of numbers for the number 1 using ListContains():

<cfoutput>
    #ListContains(variables.numList,"1")#
</cfoutput>

every number in our list would be a match because each number contains the specified "1". ListContains() does not search for a specific element that exactly matches the substring (the number 1 in our case), but merely an element that contains it.

One thing to point out is that ListFind() and ListContains() are case-sensitive. So when using them with text strings, "a" and "A" are not considered matches. However, each of these has a case-insensitive counterpart of ListFindNoCase() and ListContainsNoCase().

Remove blank elements from a list

And last but not least, here's a neat little trick I've found for removing blank elements from a list in CFML. Even though empty list elements are ignored in CFML list functions, there are times when you actually need to remove the empty elements instead of just ignore them. Simply convert it to an array and then back to a list. It's quick and dirty, but it works. The ListToArray() function has an optional "includeEmptyFields" parameter which defaults to false, so it will ignore any empty elements. Here's an example:

<cfset variables.myList = "a,b,,d,,f,g">
<cfoutput>
    #ArrayToList(ListToArray(variables.myList))#
</cfoutput>
This will output:
a,b,d,f,g


This post is part of a continuing series of CFML 101 articles. My intent is to produce a blog series aimed at the beginning CFML developer, one which helps to explain basic techniques and concepts to those new to the CF world. The topics and examples covered in this series focus on the CFML programming language in general, not a specific application server. So whether you're using ColdFusion, Railo, or Blue Dragon (referred to as CF/R/BD) to run your CFML applications, these concepts still apply.

Comments
Henry's Gravatar Isn't array generally more efficient?

With CF9 ArrayFind() and inline array literal, I found myself using list less and less.
# Posted By Henry | 12/23/09 2:20 PM
John Sieber's Gravatar I enjoyed the post and can think of many times I have used IS in if statements repetitively instead of using ListFind. Thanks for the reminder on the easier and better performing way to do it.
# Posted By John Sieber | 1/8/10 7:53 PM
Jiss's Gravatar plz enter the difference between ListFindNoCase and ListContainsNoCase functions
# Posted By Jiss | 9/17/10 3:58 AM
Eric Cobb's Gravatar @Jiss - ListFindNoCase() and ListContainsNoCase() work exactly like ListFind() and ListContains(), except their searches are case-insensitive, meaning they don't distinguish between upper and lower case letters. So when using ListFindNoCase() and ListContainsNoCase(), "a" and "A" would be considered the same thing.
# Posted By Eric Cobb | 9/23/10 8:12 AM
Jorge Pino's Gravatar I have List (^B^C^^E^^^) I want to remove all the empty value at the end of the list, but keep all other empty field

(^B^C^^E^^^) = (^B^C^^E)

how could I acomplish it

thanks
# Posted By Jorge Pino | 1/28/13 1:36 PM
Eric Cobb's Gravatar @Jorge, the first thing that comes to mind is to convert the list to an array using the optional "includeEmptyFields" argument to preserve the blanks, then loop through the array backwards and remove the blanks on the end.

<cfset myList = "^B^C^^E^^^">
<!--- convert our list to an array, setting the optional "includeemptyfields" value to "true" --->
<cfset myArray = ListToArray(myList,"^",true)>
<cfdump var="#myArray#">
<!--- Loop through our array backwards, so that we we get the blank values on the end first. --->
<cfloop from="#ArrayLen(myArray)#" to="1" step="-1"index="i">   
   <cfif not Len(Trim(myArray[i]))>
      <!--- if the array element is empty, remove it --->
      <cfset ArrayDeleteAt(myArray, i)>
   <cfelse>
      <!--- if the array element is not empty, break out of the loop. --->
      <cfbreak>
   </cfif>
</cfloop>
<!---convert the array back to a list. --->
<cfset myList = ArraytoList(myArray,"^")>
<cfdump var="#myArray#">
<cfdump var="#myList#">
<cfabort>
# Posted By Eric Cobb | 1/28/13 2:23 PM