Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with:

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

By Ben Nadel on
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:

  • <!--- 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:

  • <!--- 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

  • <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

  • <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:

  • <!--- 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:

  • <!--- 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.




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.

Reply to this Comment

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...

Reply to this Comment

@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.

Reply to this Comment

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.

Reply to this Comment

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.

Reply to this Comment

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.

Reply to this Comment

@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.

Reply to this Comment

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

Reply to this Comment

I'm going to resurrect the dead here. I just tried this with an array of numbers. Didn't work. I tried treating the numbers like text and all sorts of stuff but no matter what I do arrayfind() comes back with 0.

Reply to this Comment

@Don,

Hmm, that's odd. I just tried this:

  • <cfset data = [ 1, 2, 3, 4, 5 ] />
  •  
  • <cfoutput>
  • arrayFind( 3 ) : #arrayFind( data, 3 )#<br />
  • arrayContains( 3 ) : #arrayContains( data, 3 )#<br />
  • </cfoutput>

... and got this output:

arrayFind( 3 ) : 3
arrayContains( 3 ) : YES

I'm running ColdFusion 9.0.1. Perhaps there was a bug in the first release of CF9 with numeric values?

Reply to this Comment

@Ben, not to be a nitpicker for minor details or moreover a stickler for lean efficient code, but arrayContains() returns a boolean which is solely what an IF() test is based on. ArryFind() returns a numerical value, which is not a valid boolean type and forces an implicit conversion to a boolean response before the IF() can operate on it. So if you don't need to fetch the sub-array value, then THAT, is why you would want to use arrayContains(). Just sayin'.

Reply to this Comment

@Ben - I was curious about using a combination of arrayFind() and other validation approaches to validate incoming arguments for an API. What I'm looking for is a way to validate an incoming array of structures where each structure in the array conforms to my schema.

For Example:

  • members = [{"firstName"="Joe","lastName"="Jones","email"="joe@jones.com","someOptionalField"="defaultValueExistsInDB"}, {"firstName"="Joe","lastName"="Jones"}];

Where the second entry in the array would not be valid since it did not provide the "email" key. Where arrayFind() falls short in my case is that I don't know the values that are being provided to me. I simply want to validate the keys of each of the members in the array. I am using Taffy to take a RESTful approach to my API and the user is posting their data to me as a JSON string. The current approach I'm using that works fairly well is to essentially have a params structure that defines the dataType and key names that are required and loop over the serialized argumentCollection to ensure the existence of the keys in the argument scope. Any thoughts on how to more efficiently validate the incoming argumentCollection would be much appreciated.

Reply to this Comment

I just ran into a problem when I used ArrayFind to compare level ONE objects that have nested level TWO objects that themselves have reference to level ONE objects to which they belong.

So I got StackOverFlow exception in 80-90% cases, which is interesting. The exception wouldn't occur 100% of time.

If you think, it is probably easy to understand why StackOverFlow happens. References to the objects are looped so ColdFusion never stops going deeper.

Anyway had to change the logic by excluding reference to parent objects.

Conclusion: not good idea to use ArrayFind on objects with nested objects which have references to each over.

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.