Skip to main content
Ben Nadel at the New York ColdFusion User Group (Jan. 2009) with: Simon Free
Ben Nadel at the New York ColdFusion User Group (Jan. 2009) with: Simon Free

ColdFusion ArraySplice() Method As An Example Of A Dynamic Method Signature

By
Published in Comments (7)

Last week, I blogged about using ColdFusion's CFParam tag as a possible way to enforce CFArgument-like functionality in a situation where you might not know which arguments will exist at compile time. That post spun off into a very interesting conversation about the pros and cons of using named-arguments versus ordered-arguments and when each makes sense (or should be required).

I think the biggest factor that directed that conversation was the fact that in my example method, optional arguments could precede other optional arguments, depending on the number of arguments being passed-in. In a language like ColdFusion where different method signatures don't necessitate different physical functions, there is definitely a funky feeling to that. As such, I wanted to come up with a better example method in which the optional arguments were always at the end of the arguments list.

While this doesn't relate at all back to the concept of using CFParam to define dynamic arguments, I thought porting over a Splice() method (from Javascript) to ColdFusion, was a great example of a highly dynamic method signature. If you are not familiar with Javascript's Array::Splice() method, it can both delete 0..N elements and insert 0..N elements from and to an array at the same time.

<cffunction
	name="arraySplice"
	access="public"
	returntype="array"
	output="false"
	hint="I splice an array by deleting and / or adding new elements at the given index.">

	<!--- Define arguments. --->
	<cfargument
		name="array"
		type="array"
		required="true"
		hint="I am the array being manipulated."
		/>

	<cfargument
		name="index"
		type="numeric"
		required="true"
		hint="I am the index at which to start adding or removing elements to and from the array (respectively)."
		/>

	<cfargument
		name="howMany"
		type="numeric"
		required="true"
		hint="I am the number of elements that should be removed from the array, starting at the given index. If zero, no elements will be removed."
		/>

	<!---
		At this point, the method can take a variable number of
		subsequent arguments that would be the elements to insert
		into the array at the given index.

		<cfargument name="element1" />
		<cfargument name="element2" />
		<cfargument name="element3" />
		....
		<cfargument name="elementN" />
	--->

	<!--- Define the local scope. --->
	<cfset var local = {} />

	<!---
		The first thing we want to do is delete any elements from
		the array that we need to. Since ColdFusion doesn't have
		an inherent way to delete more than one item, we will have
		to perform this as a conditional loop.
	--->
	<cfset local.deleteCounter = arguments.howMany />

	<!---
		Keep deleting while the DeleteCounter is still non-zero
		AND while the array length is big enough to encompass the
		given index.
	--->
	<cfloop condition="(local.deleteCounter-- && (arrayLen( arguments.array ) gte arguments.index))">

		<!--- Delete the given item in the array. --->
		<cfset arrayDeleteAt(
			arguments.array,
			arguments.index
			) />

	</cfloop>

	<!---
		Now that we have delete any necessary elements from the
		array, we need to add any additional items, starting at
		the given instance.
	--->

	<!---
		Since we will need to keep updating the index of the
		insert, start a new pointer for that index.
	--->
	<cfset local.insertIndex = arguments.index />

	<!---
		Loop over the optional elements in the arguments. There
		may be ZERO or more of these elements.
	--->
	<cfloop
		index="local.elementIndex"
		from="4"
		to="#arrayLen( arguments )#"
		step="1">

		<!---
			When inserting, we need to make sure that the index
			of insertion is not beyond the bounds of the array.
			If it is, we need to do an append (or the insert
			will error).
		--->
		<cfif (local.insertIndex gt arrayLen( arguments.array ))>

			<!--- Append the new element. --->
			<cfset arrayAppend(
				arguments.array,
				arguments[ local.elementIndex ]
				) />

		<cfelse>

			<!--- Insert the new element. --->
			<cfset arrayInsertAt(
				arguments.array,
				local.insertIndex,
				arguments[ local.elementIndex ]
				) />

		</cfif>

		<!--- Increment insertion index. --->
		<cfset ++local.insertIndex />

	</cfloop>

	<!--- Return the updated array. --->
	<cfreturn arguments.array />
</cffunction>

As you can see, the first three arguments to the ArraySplice() method are always static; but then, you can append as many additional arguments to the method call as you want for the insert functionality. To see this in action, take a look at the following demo (I have created comments for the intermediary results):

<!--- Create an array of test values. --->
<cfset values = [ 1, 2, 3 ] />

<!--- Delete the last two values. --->
<cfset values = arraySplice( values, 2, 2 ) />
<!--- // [ 1 ]. --->

<!--- Insert two values at the end. --->
<cfset values = arraySplice( values, 2, 0, "A", "B" ) />
<!--- // [ 1, "A", "B" ]. --->

<!--- Insert two values at the beginning. --->
<cfset values = arraySplice( values, 1, 0, "Z", "Y" ) />
<!--- // [ "Z", "Y", 1, "A", "B" ]. --->

<!---
	Both delete the middle value and insert two more. Notice
	that delete and insert use the same index value.
--->
<cfset values = arraySplice( values, 3, 1, "*", "*" ) />
<!--- // [ "Z", "Y", "*", "*", "A", "B" ]. --->


<!--- Output the array (as a list). --->
<cfoutput>
	#arrayToList( values, ", " )#
</cfoutput>

When we run this combination of deletes and inserts, we get the following array-to-list output:

Z, Y, *, *, A, B

Nothing too technically savvy going on here, other than the CFLoop condition that both sets and tests a value (sweeet!); mostly, I thought the ArraySplice() method was just an elegant example of a method signature that could take a variable number of arguments.

Want to use code from this post? Check out the license.

Reader Comments

11 Comments

Would using the built in java functions be faster on large arrays?

exp:

<cfset var myArray = createObject("java", "java.util.ArrayList").Init(arguments.array) />

...

<cfreturn myArray.subList(JavaCast("int", arguments.index - 1), JavaCast("int", howMany)) />

15,848 Comments

@Christopher,

I think using the underlying Java stuff is definitely exciting and often times adds efficiencies. I just worry sometimes about using undocumented stuff; I used to be very pro it, but people have made me wear of it.

Perhaps it's time to rebuild my courage :)

167 Comments

Nice to see someone giving CF a little "dynamic method signature" discussion love.

Intelligently overloaded function signatures are one of the most powerful techniques for quality API authoring. jQuery's selector method is probably the greatest single example of how you can consolidate an entire spectrum of implementation and functionality into a single, pristine entry point. A lot of the CFML extension utilities I use behave differently depending on the method signature. One side effect I've noticed is that the more flexible and multifunctional the API functions you author are, ironically, the easier it becomes to remember those variant signatures and what they do. Great post.

15,848 Comments

@David,

Glad you like. It is some really cool functionality, but one that I have not had the best chance to use yet (or at least not that I have thought of).

1 Comments

@ Ben-- Re: inserting and testing insert position--why not iterate the insert values backward and then reuse the same insert position? (Yes, I realize this is years after your post, but I think this is an... evergreen(?) response.)

15,848 Comments

@Neil,

Years later... but, that's actually a pretty clever idea. If you iterate backwards, you're totally right, less calculation in the insertion, but the same overhead (as far as I can see). Good thinking!

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel