Learning ColdFusion 9: ArrayFind() And ArrayContains() For Searching Arrays

Posted July 18, 2009 at 7:37 PM

Tags: ColdFusion

ColdFusion 9 had added three new methods for finding values within an array. Two of them - ArrayFind() and ArrayFindNoCase() - return the index of the target value (or zero if it the value cannot be found). The third, ArrayContains(), returns a boolean as to whether or not the value was found in the array. From what I can see, I can't figure out why anyone would want to use the ArrayContains() method. Since ColdFusion can already treat numeric values as booleans, it seems that the index returned by ArrayFind() would be just as useful in any case that someone would want to use ArrayContains().

That said, let's look at how these new ColdFusion 9 array functions work. They work with both simple and complex values, so let's try using simple values first:

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

  • <!--- Create an array of simple girls. --->
  • <cfset girls = [
  • "Tricia",
  • "Joanna",
  • "Libby"
  • ] />
  •  
  • <!--- Output results with existing value. --->
  • With Existint Values<br />
  •  
  • ArrayFind: #arrayFind( girls, "Joanna" )#<br />
  • ArrayContains: #arrayContains( girls, "Joanna" )#<br />
  •  
  • <br />
  •  
  • <!--- Output results with non-existing values. --->
  • With Non-Existing Values<br />
  •  
  • ArrayFind: #arrayFind( girls, "Molly" )#<br />
  • ArrayContains: #arrayContains( girls, "Molly" )#<br />
  •  
  • <br />
  •  
  • <!--- Output result with existing partial values. --->
  • ArrayFind: #arrayFind( girls, "Lib" )#<br />
  • ArrayContains: #arrayContains( girls, "Lib" )#<br />

Here, we create an array of simple values and then we test three different scenarios:

  1. The target value exists.
  2. The target value does not exist.
  3. The target value is a substring of an existing value. I tried this one because with lists, the difference between ColdFusion's ListContains() method and ListFind() method is that ListContains() will match substrings within a list element. I wanted to see if the same held true for ArrayContains().

When we run the above code, we get the following output:

With Existing Values
ArrayFind: 2
ArrayContains: YES

With Non-Existing Values
ArrayFind: 0
ArrayContains: NO

With Existing Substring
ArrayFind: 0
ArrayContains: NO

As you can see from the results, the ArrayFind() and ArrayContains() both work using full-matches on simple values. I guess, unlike its list-counterpart, the ArrayContains() won't match substrings. The ArrayFind() method correctly returned the index of the matching girl and ArrayContains() method correctly returned that the target girl was, in fact, in the list.

Being able to find simple values within an array is definitely awesome - I'm not going to knock it in any way. I think this is going to be an excellent new feature, and most likely, the most common use case. But, let's see how this works with the next level of complex objects: arrays and structs:

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

  • <!--- Create an array of mixed-type complex objects. --->
  • <cfset data = [
  • [ "Tricia", "Joanna", "Libby" ],
  • {
  • hotGirls = [ "Tricia", "Joanna", "Libby" ]
  • },
  • {
  • athleticGirls = [ "Tricia", "Joanna" ]
  • }
  • ] />
  •  
  •  
  • <!--- Search for a top-level existing array. --->
  • Existing Top-Level Array<br />
  •  
  • #arrayFind(
  • data,
  • [ "Tricia", "Joanna", "Libby" ]
  • )#<br />
  •  
  • #arrayContains(
  • data,
  • [ "Tricia", "Joanna", "Libby" ]
  • )#<br />
  •  
  • <br />
  •  
  • <!--- Search for a top-level existing struct. --->
  • Existing Top-Level Struct<br />
  •  
  • #arrayFind(
  • data,
  • {
  • athleticGirls = [ "Tricia", "Joanna" ]
  • }
  • )#<br />
  •  
  • #arrayContains(
  • data,
  • {
  • athleticGirls = [ "Tricia", "Joanna" ]
  • }
  • )#<br />
  •  
  • <br />
  •  
  • <!--- Search for a sub-level existing array. --->
  • Existing Sub-Level Array<br />
  •  
  • #arrayFind(
  • data,
  • [ "Tricia", "Joanna" ]
  • )#<br />
  •  
  • #arrayContains(
  • data,
  • [ "Tricia", "Joanna" ]
  • )#<br />

Here, we create an array containing both arrays and structures and then we test three different scenarios:

  1. The target array exists at the top level.
  2. The target struct exists at the top level.
  3. The target array exists at a nested sub-level of the array.

I didn't bother testing for non-existent values because we established that that worked properly in the first demo. When we run the above code, we get the following output:

Existing Top-Level Array
1
YES

Existing Top-Level Struct
3
YES

Existing Sub-Level Array
0
NO

First of all, let's just acknowledge that the array in question can contain mixed data types; our array contained both structs and arrays and both functions worked without error. Both ArrayFind() and ArrayContains() were able to find the top-level values, but not the nested value. I didn't expect this to work with ArrayFind(), but I tried the nested scenario, again, to see if there was any logical difference between ArrayFind() and ArrayContains(). So far, there doesn't seem to be any.

When it comes to finding the struct in the above demo, realize that the struct is being found based on value and not on instance. The struct within the array was a physically different instance than the struct we passed as an argument to the ArrayFind() and ArrayContains() methods; but, it was found nonetheless. Therefore, we can assume that the target equality is based on value and not on any kind of pointer logic.

Now, let's take the level of data complexity up a bit and see if these methods work with ColdFusion components. To test this thoroughly, I'm going to create two ColdFusion components - one that stores its values publicly (THIS scope) and one that stores its values privately (VARIABLES scope).

PublicGirl.cfc

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

  • <cfcomponent
  • output="false"
  • hint="I am a public sort of girl.">
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I initialize the component.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the name property."
  • />
  •  
  • <!---
  • Define instance variables using the public THIS
  • scope so that all instance variables are available
  • externally.
  • --->
  • <cfset this.name = arguments.name />
  •  
  • <!--- Return this object. --->
  • <cfreturn this />
  • </cffunction>
  •  
  • </cfcomponent>

PrivateGirl.cfc

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

  • <cfcomponent
  • output="false"
  • hint="I am a private sort of girl.">
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I initialize the component.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the name property."
  • />
  •  
  • <!---
  • Define instance variables using the private
  • VARIABLES scope so that none of the instance
  • variables are available externally.
  • --->
  • <cfset variables.name = arguments.name />
  •  
  • <!--- Return this object. --->
  • <cfreturn this />
  • </cffunction>
  •  
  • </cfcomponent>

Notice that the only difference between these two ColdFusion components is the use of scope - the former stores the Name property in the THIS scope whereas the latter stores it in the VARIABLES scope. Now that we have these set up, we can run some tests:

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

  • <!--- Create an array of public ColdFusion components. --->
  • <cfset girls = [
  • new PublicGirl( "Tricia" ),
  • new PublicGirl( "Joanna" ),
  • new PublicGirl( "Libby" )
  • ] />
  •  
  • <!--- Search for an existing girl. --->
  • Existing Public Girl<br />
  •  
  • #arrayFind(
  • girls,
  • new PublicGirl( "Tricia" )
  • )#<br />
  •  
  • #arrayContains(
  • girls,
  • new PublicGirl( "Tricia" )
  • )#<br />
  •  
  • <br />
  •  
  • <!--- Search for a non-existing girl. --->
  • Non-Existing Public Girl<br />
  •  
  • #arrayFind(
  • girls,
  • new PublicGirl( "Molly" )
  • )#<br />
  •  
  • #arrayContains(
  • girls,
  • new PublicGirl( "Molly" )
  • )#<br />

Notice that in this test, the ColdFusion component we are searching for is not a component that is tully in the array. Rather, we are trying to find a new instance of the PublicGirl.cfc in the target array. When we run the above code, we get the following output:

Existing Public Girl
1
YES

Non-Existing Public Girl
0
NO

Just as with array and struct values, it seems that both ArrayFind() and ArrayContains() both work based on compared values and not on compared references. This makes me wonder how if having a CFC with private instance variables will work:

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

  • <!--- Create an array of private ColdFusion components. --->
  • <cfset girls = [
  • new PrivateGirl( "Tricia" ),
  • new PrivateGirl( "Joanna" ),
  • new PrivateGirl( "Libby" )
  • ] />
  •  
  • <!--- Search for an existing girl. --->
  • Existing Private Girl<br />
  •  
  • #arrayFind(
  • girls,
  • new PrivateGirl( "Tricia" )
  • )#<br />
  •  
  • #arrayContains(
  • girls,
  • new PrivateGirl( "Tricia" )
  • )#<br />
  •  
  • <br />
  •  
  • <!--- Search for a non-existing girl. --->
  • Non-Existing Private Girl<br />
  •  
  • #arrayFind(
  • girls,
  • new PrivateGirl( "Molly" )
  • )#<br />
  •  
  • #arrayContains(
  • girls,
  • new PrivateGirl( "Molly" )
  • )#<br />

When we run this code, we get the following output:

Existing Private Girl
1
YES

Non-Existing Private Girl
0
NO

Now this is very interesting! Before we did the private test, we had to assume that ColdFusion was doing value comparisons and not reference comparisons. But, both the ArrayFind() and ArrayContains() method correctly determined that the non-existing PrivateGirl.cfc instance didn't exist in the target array even though, according to public values, it should have. Because the two CFCs that we are comparing (at each array index) are, indeed, difference physical instances, ColdFusion must be comparing some sort of Java hash value within the objects. Very cool!

The new array searching methods offered in ColdFusion 9 are pretty nice. In my demonstrations, I only tested ArrayFind(), but there is also an ArrayFindNoCase() method which performs searches without case sensitivity. For some reason, there is no ArrayContainsNoCase() method. But, that doesn't really matter as the ArrayContains() method appears to be completely useless. From what I can tell, it offers absolutely no advantage over its ArrayFind() sister methods.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page


You Might Also Be Interested In:



Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Jul 18, 2009 at 8:59 PM // reply »
41 Comments

Not to steal this post, but is there any chance you'd be willing to post in the new CFScript? I'm yet to see anyone really talking about CF9 in the new syntax.


Jul 18, 2009 at 9:21 PM // reply »
2 Comments

Is there maybe a performance difference between ArrayFind() and ArrayContains()?


Jul 19, 2009 at 4:18 AM // reply »
18 Comments

I'd be interested to see how it performs compared
to loading the values in a struct and doing a structkeyExists, a very common pattern i use to date

I have seen a lot of ugly repeated array searching code in CF over the years...


Jul 19, 2009 at 11:24 AM // reply »
6,516 Comments

@David,

I'm a tag man at heart, but I would be more than happy to write up a post on the new script enhancements, no problem.

@Nick,

Hmm, that could be. I can't help, though, but imagine that ArrayContains() is nothing more than:

return( arrayFind( array, value ) NEQ 0 )

@Zac,

The only problem with that is that ArrayFind() and ArrayContains() work with complex objects and components; StructKeyExists(), while a good approach, cannot do this.


Jul 19, 2009 at 12:13 PM // reply »
102 Comments

Is the ArrayContains vs ArrayFind the same as ListFind vs ListContains where ListContains will actually match partial values? So if you do a search for "car" in a list, it will find instances of "car", but will also find "cart" or "scared", etc.


Jul 19, 2009 at 12:19 PM // reply »
6,516 Comments

@Gareth,

No, ArrayContains() does not seem to do any partial matching.


Jul 19, 2009 at 6:48 PM // reply »
27 Comments

Ahh!!! finally!!!

Good, thanks adobe...

Goodbye, stupid: ListFind(ArrayToList(arr),"urgh!")...


Jul 19, 2009 at 11:34 PM // reply »
6,516 Comments

@Henry,

Ha ha, word up :)


Jul 20, 2009 at 9:44 AM // reply »
5 Comments

With CF8 I have been using the underlying java methods on arrays with array.contains() and array.indexof(). The indexof() method has a 0 based index so you have to correct for it and it is case sensitive in the search.

http://coldfused.blogspot.com/2007/01/extend-cf-native-objects-harnessing.html

Either way these are welcomed functions that should have been in there before.


Jul 20, 2009 at 6:48 PM // reply »
12 Comments

Brandon,

That's exactly what I was going to say. Since CF array is essentially a Java Vector, Adobe has just opened up few new methods from an existing API. Nevertheless welcome additions.


Jul 21, 2009 at 9:43 AM // reply »
3 Comments

Does arrayFind return the index of the first or the last match?
Maybe arrayContains breaks and returns after finding the first match. That could be useful for performance if you have a very large array with many duplicate values.


Jul 21, 2009 at 6:22 PM // reply »
6,516 Comments

@Brandon, @Qasim,

Yeah, definitely cool stuff. It's about time some of the Java methods went from "undocumented" to "part of the language."

@Seth,

I believe they both quite after the first match.


Jul 26, 2009 at 1:16 PM // reply »
19 Comments

This is SWEEEET!!!! Usually I would use java to accomplish such functionality but this is a welcomed addition to ColdFusion.


Jul 27, 2009 at 8:37 AM // reply »
6,516 Comments

@Hussein,

Agreed - this will be sweet!


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 20, 2009 at 11:00 PM
Five Months Without Hungarian Notation And I'm Loving It
@Marcel, Yeah, I always err on the side of longer but more readable variable names. As for the camel casing of CF methods and the headless camel casing of custom items, I get around this by always ... read »
Nov 20, 2009 at 10:56 PM
Five Months Without Hungarian Notation And I'm Loving It
I use the following and love it: my.namespace.MyComponents.functionMethodsOrUDF() CONSTANT_VALUES_OR_PROPERTIES One thing I always try is to CamelCaseBuiltInColdFusionFunctions() so others can tell ... read »
Nov 20, 2009 at 5:38 PM
Learning ColdFusion 8: CFImage Part I - Reading And Writing Images
Hi Ben, Great article. I've been looking around to see if ColdFusion image engine can programatically create the following "wrap around" effect: http://www.creativepro.com/article/photoshop-s-she ... read »
Nov 20, 2009 at 5:35 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Dave: I talked to Gert he suggested: <cfhttp method="get" url="http://{some cf website}" result="stuff" addtoken="yes" /> Note the addition of cfhttp attribute addtoken. That should persist y ... read »
Nov 20, 2009 at 5:23 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Todd, Ahh, gotcha, yeah that makes sense. ... read »
Nov 20, 2009 at 5:17 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
Ben, sorry if I didn't make this clear. You can make it work like that if you want, just put <cfset session.foo = 1> (and <cfset application.foo = 1>) in your OnRequestStart() and it reve ... read »
Nov 20, 2009 at 5:07 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
@Todd, I have seen tidbits about the way Railo handles session. I can understand that it lazy-loads sessions, but I also think that I might make some things more complicated. For example, often tim ... read »
Nov 20, 2009 at 4:53 PM
Maintaining ColdFusion Sessions Across SMS Text Message Requests Without Cookies
Ben, you can ramp up the security by turning on J2EE session which gives you a third set of numbers other than CFID/CFTOKEN. There's a reason why ACF put this in place (other than just session replic ... read »