I wanted to throw my two cents in here. I ran into a case where I needed to sort an array of structures using more than one key. I wound up using a constructed query to do my sorting. The function takes the array of structs as the first argument, and then an array of structs indicating the sort order, like this:
<cfset result = sortArrayOfStructsUsingQuery(myArrayOfStructs,[
{name = "price", type = "decimal", sortOrder = "asc"},
{name = "id", type = "integer", sortOrder = "asc"}
])>
Within the sortArrayOfStructsUsingQuery function, I construct a query based only on the keys I pass in, then sort that query. Then, I loop over the query, find the structure element from the array which matches the data at the current query row, and add that structure to the array I hand back.
It's entirely possible there's a gaping hole in this code that my testing hasn't uncovered (there haven't been a lot of use-cases for me yet), but in case it's useful to anybody, here it is. Hope it's useful, and if there are any glaring holes, I'm happy to hear about them.
(just a note: I use the "local" scope for all variables that will stay in the function, and the "r" scope for anything I intend to hand back, for whatever that's worth)
<cffunction name="sortArrayOfStructsUsingQuery" output="yes" returnType="array">
<cfargument name="array" type="array" required="true">
<cfargument name="sortKeys" type="array" required="true">
<cfset var local = {
order = {
keyList = "",
typeList = "",
clause = ""
},
array = duplicate(arguments.array),
newArray = []
}>
<cfset var r = {
array = []
}>
<cftry>
<!--- build necessary lists out of given sortKeys array --->
<cfloop array=#arguments.sortKeys# index="local.key">
<cfset local.order.keyList = listAppend(local.order.keyList, local.key.name)>
<cfset local.order.typeList = listAppend(local.order.typeList, local.key.type)>
<cfset local.order.clause = listAppend(local.order.clause, "#local.key.name# #local.key.sortOrder#")>
</cfloop>
<!--- build query of the relevant sortKeys --->
<cfset local.query = queryNew(local.order.keyList, local.order.typeList)>
<cfloop array=#arguments.array# index="local.obj">
<cfset queryAddRow(local.query)>
<cfloop list=#local.order.keyList# index="local.key">
<cfset querySetCell(local.query, local.key, structFind(local.obj, local.key))>
</cfloop>
</cfloop>
<!--- sort the query according to keys --->
<cfquery name="local.sortedQuery" dbtype="query">
SELECT *
FROM [local].query
ORDER BY #local.order.clause#
</cfquery>
<!--- rebuild the array based on the sorted query, then hand the sorted array back --->
<cfloop query="local.sortedQuery">
<cfloop from=1 to=#arraylen(local.array)# index=local.i>
<cfset local.matchP = true>
<cfloop list=#local.order.keylist# index="local.key">
<cfif structKeyExists(local.array[local.i], local.key)
AND structFind(local.array[local.i], local.key) EQ evaluate("local.sortedQuery.#local.key#")>
<cfset local.matchP = true>
<cfelse>
<cfset local.matchP = false>
<cfbreak>
</cfif>
</cfloop>
<cfif local.matchP>
<cfset arrayAppend(r.array, local.array[local.i])>
<cfelse>
<cfif NOT arrayContains(local.newArray, local.array[local.i])>
<cfset arrayAppend(local.newArray, local.array[local.i])>
</cfif>
</cfif>
</cfloop>
<cfset local.array = local.newArray>
</cfloop>
<!--- Outbound array should contain the same number of elements as inbound array --->
<cfif arrayLen(r.array) NEQ arrayLen(arguments.array)>
<!--- log an error here --->
<cfset r.array = arguments.array>
</cfif>
<cfcatch type="any">
<!--- log an error here --->
<cfset r.array = arguments.array>
</cfcatch>
</cftry>
<cfreturn r.array>
</cffunction>