Comment trier un tableau de structures dans ColdFusion

j'ai un tableau de structures dans ColdFusion. Je voudrais trier ce tableau basé sur l'un des attributs dans leurs structures. Comment puis-je y parvenir? J'ai trouvé la fonction StructSort, mais elle prend une structure et j'ai un tableau.

si ce n'est pas possible purement en ColdFusion, est-ce possible en Java d'une façon ou d'une autre (peut-être en utilisant Arrays.sort(Object[], Comparator))?

17
demandé sur Kip 2010-04-16 18:45:13

9 réponses

comme d'habitude, CFLib.org a exactement ce que vous voulez.

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;
}
12
répondu ale 2013-06-07 06:38:35

voici quelque chose qui ressemble de près à l'original StructSort(). Il prend également en charge l' 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>

Utilisation / 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>

Résultat:

ArrayOfStructSort Result

9
répondu Tomalak 2015-10-19 18:51:47

la solution acceptée (à partir de CFLib.org) is NOT safe. J'ai expérimenté avec cela pour quelque chose que je devais faire au travail et ai trouvé qu'il renvoie des résultats incorrects lors du tri numérique avec des flotteurs.

Par exemple si j'ai ces structures: (pseudo-code)


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');

parcourir le tableau trié et imprimer le nom et le poids. Il ne sera pas dans le bon ordre, et c'est une limitation de mélange une clé arbitraire avec la valeur triée.

5
répondu jpswain 2010-07-30 23:21:06

Vous pouvez utiliser le trait de Soulignement.Bibliothèque du cfc pour réaliser ce que vous voulez:

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

_ = new Underscore();

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

trait de Soulignement.cfc vous permet de définir un comparateur personnalisé et délègue à arraySort(). Vous pouvez l'utiliser pour trier les tableaux, les structures, les requêtes ou les listes de chaînes, mais il retourne toujours un tableau.

(Disclaimer: j'ai écrit le trait de Soulignement.cfc)

4
répondu Russ 2013-11-06 02:42:31

je voulais jeter mes deux cents ici. J'ai rencontré un cas où j'avais besoin de trier un ensemble de structures en utilisant plus d'une clé. J'ai fini par utiliser une requête construite pour faire mon tri. La fonction prend le tableau de structures comme premier argument, et puis un tableau de structures en indiquant l'ordre de tri, comme ceci:

<cfset result = sortArrayOfStructsUsingQuery(myArrayOfStructs,[
{name = "price", type = "decimal", sortOrder = "asc"},
{name = "id", type = "integer", sortOrder = "asc"}
])>

dans la fonction sortArrayOfStructsUsingQuery, je construis une requête basée uniquement sur les clés que je passe, puis je trie cette requête. Ensuite, je boucle sur la requête, trouver l'élément de structure du tableau qui correspond aux données à la ligne de requête courante, et ajouter cette structure au tableau que je rends.

il est tout à fait possible qu'il y ait un trou béant dans ce code que mes tests n'ont pas découvert (il n'y a pas eu beaucoup de cas d'utilisation pour moi encore), mais au cas où il serait utile à quelqu'un, le voici. J'espère que c'est utile, et s'il y a des trous visibles, je suis content d'en entendre parler.

<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>
2
répondu hairbo 2012-12-13 17:03:45

c'est en fait encore plus facile avec le nouveau soutien de fermeture des FC.

Voici un exemple, j'ai travaillé aujourd'hui où j'ai voulu trier un tableau de structures par une date stockée dans la structure. Je faisais le tri par ordre décroissant.

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

Je ne peux pas prendre tout le crédit que je l'ai adapté de Ray Camden's sur les fermetures de 2012.

2
répondu mikest34 2013-10-07 21:37:57

voici un UDF basé sur la réponse de Tomalak qui supporte également les objets personnalisés (par exemple, utilisé par certains CMSs basés sur Railo). Cette fonction est compatible avec 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>
2
répondu thdoan 2015-10-16 10:59:59

Je n'ai pas la réputation de commenter sur @mikest34 post ci-dessus mais @russ avait raison que ce rappel ne fonctionne plus de la façon dont il a été expliqué.

C'est Adam Cameron qui a découvert que l'utilisation de arraySort avec un callback ne nécessite plus une réponse True/False mais plutôt:

-1, si le premier paramètre est "plus petit" que le deuxième paramètre

0, si le premier paramètre est égal au second paramètre

1, première le paramètre est "plus grand" que le deuxième paramètre

donc le bon rappel est:

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

Essais et de travail dans CF2016

2
répondu DaveB 2017-12-08 18:21:04

Dans le cas où vous ne voulez pas utiliser de méthodes personnalisées, Coldfusion a structSorthttp://www.cfquickdocs.com/cf8/#StructSort