I have an array of structs in ColdFusion. I\'d like to sort this array based on one of the attributes in the structs. How can I achieve this? I\'ve found the StructSort fun
The accepted solution (from CFLib.org) is NOT safe. I experimented with this for something I needed to do at work and found that it returns incorrect results when sorting numeric with floats.
For example if I have these structs: (pseudocode)
a = ArrayNew(1);
s = StructNew();
s.name = 'orange';
s.weight = 200;
ArrayAppend(a, s);
s = StructNew();
s.name = 'strawberry';
s.weight = 28;
ArrayAppend(a, s);
s = StructNew();
s.name = 'banana';
s.weight = 90.55;
ArrayAppend(a, s);
sorted_array = arrayOfStructsSort(a, 'weight', 'asc', 'numeric');
Iterate over the sorted array and print the name & weight. It won't be in the right order, and this is a limitation of mixing an arbitrary key with the value being sorted.
Easy solution to sort an array of structures using more than one key using arraySort callback:
It takes array of structs to be sorted as first parameter and array of structs in format of sortkey/sortorder pair as second parameter e.g. [{sortkey: 'FirstName', sortorder: 'asc'}, {sortkey: 'LastName', sortorder: 'desc'}].
<cffunction name="arrayOfStructsSort" access="public" returntype="array" output="false" hint="This sorts an array of structures.">
<cfargument name="aOfS" type="array" required="yes" />
<cfargument name="key_sortOrder" type="array" required="yes" />
<cfscript>
arraySort(
aOfS,
function (a, b) {
for (var i = 1; i lte arrayLen(key_sortOrder); i = i + 1) {
var prop = key_sortOrder[i];
var key = prop.key;
var sortOrder = prop.sortOrder;
if (a[key] lt b[key]) {
if (sortOrder eq 'desc') {
return 1;
} else {
return -1;
}
}
if (a[key] gt b[key]) {
if (sortOrder eq 'desc') {
return -1;
} else {
return 1;
}
}
}
return 0;
}
);
return aOfS;
</cfscript>
</cffunction>
Simply call it with:
<cfset ArraySorted = arrayOfStructsSort(arrayToBeSorted,arrayOfSorkeys)>
In case you don't want to use custom methods, Coldfusion has structSort method http://www.cfquickdocs.com/cf8/#StructSort . Yes it sorts structure with nested structures, BUT returns array so could be used to achieve same result.
Here is something that closely resembles the original StructSort()
. It also supports the pathToSubElement
argument.
<cffunction name="ArrayOfStructSort" returntype="array" access="public" output="no">
<cfargument name="base" type="array" required="yes" />
<cfargument name="sortType" type="string" required="no" default="text" />
<cfargument name="sortOrder" type="string" required="no" default="ASC" />
<cfargument name="pathToSubElement" type="string" required="no" default="" />
<cfset var tmpStruct = StructNew()>
<cfset var returnVal = ArrayNew(1)>
<cfset var i = 0>
<cfset var keys = "">
<cfloop from="1" to="#ArrayLen(base)#" index="i">
<cfset tmpStruct[i] = base[i]>
</cfloop>
<cfset keys = StructSort(tmpStruct, sortType, sortOrder, pathToSubElement)>
<cfloop from="1" to="#ArrayLen(keys)#" index="i">
<cfset returnVal[i] = tmpStruct[keys[i]]>
</cfloop>
<cfreturn returnVal>
</cffunction>
Usage / test:
<cfscript>
arr = ArrayNew(1);
for (i = 1; i lte 5; i = i + 1) {
s = StructNew();
s.a.b = 6 - i;
ArrayAppend(arr, s);
}
</cfscript>
<cfset sorted = ArrayOfStructSort(arr, "numeric", "asc", "a.b")>
<table><tr>
<td><cfdump var="#arr#"></td>
<td><cfdump var="#sorted#"></td>
</tr></table>
Result: