Learning ColdFusion 9: ArrayFind() And ArrayContains() For Searching Arrays
Posted July 18, 2009 at 7:37 PM
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:
- The target value exists.
- The target value does not exist.
- 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:
- The target array exists at the top level.
- The target struct exists at the top level.
- 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 | Other Searches | Print Page
Newer Post
Learning ColdFusion 9: IsNull() And Working With NULL Values
Older Post
The Performance Cost Of Throwing Exceptions In ColdFusion
Reader 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.
Is there maybe a performance difference between ArrayFind() and ArrayContains()?
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...
@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.
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.
@Gareth,
No, ArrayContains() does not seem to do any partial matching.
Ahh!!! finally!!!
Good, thanks adobe...
Goodbye, stupid: ListFind(ArrayToList(arr),"urgh!")...
@Henry,
Ha ha, word up :)
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.
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.
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.
@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.
This is SWEEEET!!!! Usually I would use java to accomplish such functionality but this is a welcomed addition to ColdFusion.
@Hussein,
Agreed - this will be sweet!




