How to sort an array of structs in ColdFusion

后端 未结 10 1650
余生分开走
余生分开走 2020-12-17 10:00

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

相关标签:
10条回答
  • 2020-12-17 10:15

    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

    0 讨论(0)
  • 2020-12-17 10:15

    Here's a UDF based on Tomalak's answer that also supports custom objects (e.g., used by some Railo-based CMSs). This function is compatible with ColdFusion 9.

    <cffunction name="sortStructArray" returntype="array" access="public">
      <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 _sct = StructNew()>
      <cfset var _aryKeys = ArrayNew(1)>
      <cfset var arySorted = ArrayNew(1)>
      <cfif IsStruct(base[1])>
        <!--- Standard structure --->
        <cfloop from="1" to="#ArrayLen(base)#" index="i">
          <cfset _sct[i] = base[i]>
        </cfloop>
        <cfset _aryKeys = StructSort(_sct, sortType, sortOrder, pathToSubElement)>
        <cfloop from="1" to="#ArrayLen(_aryKeys)#" index="i">
          <cfset arySorted[i] = _sct[_aryKeys[i]]>
        </cfloop>
      <cfelse>
        <!--- Custom object (e.g., Catalog) --->
        <cfloop from="1" to="#ArrayLen(base)#" index="i">
          <cfset _sct[i] = StructNew()>
          <cfset _sct[i][pathToSubElement] = base[i][pathToSubElement]>
        </cfloop>
        <cfset _aryKeys = StructSort(_sct, sortType, sortOrder, pathToSubElement)>
        <cfloop from="1" to="#ArrayLen(_aryKeys)#" index="i">
          <cfset arySorted[i] = base[_aryKeys[i]]>
        </cfloop>
      </cfif>
      <cfreturn arySorted>
    </cffunction>
    
    0 讨论(0)
  • 2020-12-17 10:16

    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>
    
    0 讨论(0)
  • 2020-12-17 10:17

    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 (nathan@changemedia.com)
    * @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;
    }
    
    0 讨论(0)
  • 2020-12-17 10:21

    It's actually even easier with the new CF Closure support.

    Here's an example I worked on today where I wanted to sort an array of structs by a date stored in the struct. I was sorting in descending order.

    ArraySort(yourArrayOfStructs, function(a,b) {
        if ( DateCompare(a.struct_date, b.struct_date) == -1 ) {
            return true;
        } else {
            return false;
        }
    });
    

    I can't take total credit as I adapted this from Ray Camden's on Closures from 2012.

    0 讨论(0)
  • 2020-12-17 10:23

    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)

    0 讨论(0)
提交回复
热议问题