How to sort an array of structs in ColdFusion

19,813

Solution 1

As usual, CFLib.org has exactly what you want.

http://cflib.org/udf/ArrayOfStructsSort

/**
* Sorts an array of structures based on a key in the structures.
*
* @param aofS      Array of structures.
* @param key      Key to sort by.
* @param sortOrder      Order to sort by, asc or desc.
* @param sortType      Text, textnocase, or numeric.
* @param delim      Delimiter used for temporary data storage. Must not exist in data. Defaults to a period.
* @return Returns a sorted array.
* @author Nathan Dintenfass ([email protected])
* @version 1, December 10, 2001
*/
function arrayOfStructsSort(aOfS,key){
        //by default we'll use an ascending sort
        var sortOrder = "asc";        
        //by default, we'll use a textnocase sort
        var sortType = "textnocase";
        //by default, use ascii character 30 as the delim
        var delim = ".";
        //make an array to hold the sort stuff
        var sortArray = arraynew(1);
        //make an array to return
        var returnArray = arraynew(1);
        //grab the number of elements in the array (used in the loops)
        var count = arrayLen(aOfS);
        //make a variable to use in the loop
        var ii = 1;
        //if there is a 3rd argument, set the sortOrder
        if(arraylen(arguments) GT 2)
            sortOrder = arguments[3];
        //if there is a 4th argument, set the sortType
        if(arraylen(arguments) GT 3)
            sortType = arguments[4];
        //if there is a 5th argument, set the delim
        if(arraylen(arguments) GT 4)
            delim = arguments[5];
        //loop over the array of structs, building the sortArray
        for(ii = 1; ii lte count; ii = ii + 1)
            sortArray[ii] = aOfS[ii][key] & delim & ii;
        //now sort the array
        arraySort(sortArray,sortType,sortOrder);
        //now build the return array
        for(ii = 1; ii lte count; ii = ii + 1)
            returnArray[ii] = aOfS[listLast(sortArray[ii],delim)];
        //return the array
        return returnArray;
}

Solution 2

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:

ArrayOfStructSort Result

Solution 3

I don't have the reputation points to comment on @mikest34 post above but @russ was correct that this callback no longer works the way it was explained.

It was Adam Cameron who discovered that when using arraySort with a callback, it no longer requires a True/False response but rather:

-1, if first parameter is "smaller" than second parameter
0, if first parameter is equal to second parameter
1, first parameter is "bigger" than second parameter

So the correct callback is:

ArraySort(yourArrayOfStructs, function(a,b) {
    return compare(a.struct_date, b.struct_date);
});

Testing and working in CF2016

Solution 4

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.

Solution 5

You can use the Underscore.cfc library to accomplish what you want:

arrayOfStructs = [
    {myAttribute: 10},
    {myAttribute: 30},
    {myAttribute: 20}
];

_ = new Underscore();

sortedArray = _.sortBy(arrayOfStructs, function (struct) {
    return struct.myAttribute;
});

Underscore.cfc allows you to define a custom comparator and delegates to arraySort(). You can use it for sorting arrays, structs, queries, or string lists, but it always returns an array.

(Disclaimer: I wrote Underscore.cfc)

Share:
19,813
Kip
Author by

Kip

I've been programming since I got my hands on a TI-83 in precalculus class during junior year of high school. Some cool stuff I've done: Chord-o-matic Chord Player: find out what those crazy chords are named! Everytime: keep track of the current time in lots of time zones from your system tray BigFraction: open source Java library for handling fractions to arbitrary precision. JSON Formatter: a completely client-side JSON beautifier/uglifier. QuickReplace: a completely client-side regex tool. It's behind some ugly developer UI since I created it for myself to use. (Sorry not sorry.)

Updated on June 06, 2022

Comments

  • Kip
    Kip almost 2 years

    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 function, but it takes a structure and I have an array.

    If this is not possible purely in ColdFusion, is it possible in Java somehow (maybe using Arrays.sort(Object[], Comparator))?

  • Edward M Smith
    Edward M Smith about 14 years
    "keys" needs to be var-scoped, I believe.
  • Tomalak
    Tomalak about 14 years
    @Edward: Absolutely, I've missed that one. Thanks for the hint.
  • Adam Tuttle
    Adam Tuttle over 11 years
    Good information to share, but as you're not proposing an alternate solution this should be in a comment on that answer. You could put the code sample into a gist/pastebin/etc so that it would fit.
  • Peter Boughton
    Peter Boughton over 10 years
    Or function(a,b){ return ( a.struct_date < b.struct_date ); }
  • Kip
    Kip over 10 years
    is this only in CF 10?
  • Peter Boughton
    Peter Boughton over 10 years
    Inline function expressions and closures were added with CF10 and Railo 4.0, as was the updated ArraySort. You've always been able to pass UDF as arguments, but none of the built-in functions had args that accepted functions previously. They still don't (currently) allow BIFs, but that'll hopefully change in next version.
  • mikest34
    mikest34 over 10 years
    Thanks Peter. I also realized there were easier approaches after posting.
  • Russ
    Russ over 10 years
    Beware that the implementation of arraySort() may have changed: cfmlblog.adamcameron.me/2013/07/…
  • thdoan
    thdoan over 8 years
    How would you use structSort() to sort an array of structs?
  • Tomalak
    Tomalak over 8 years
    Nice one. I was about to look into my own answer, but I guess I can delay that for a bit now...
  • Kevin Morris
    Kevin Morris over 7 years
    Many of the other answers here depend on the arraySort() callback feature (added in CF10) or sort() member function (added in CF11). Tomalak's answer works at least back to CF9, which I still have to support. Thank you, Tomalak!
  • osoblanco
    osoblanco over 4 years
    thanks! for me, I was sorting a query result so it wound up being result.sort(function(a,b){....});