2010-04-16 5 views
17

J'ai un tableau de structures dans ColdFusion. J'aimerais trier ce tableau en fonction de l'un des attributs des structures. Comment puis-je atteindre cet objectif? J'ai trouvé la fonction StructSort, mais il faut une structure et j'ai un tableau.Comment trier un tableau de structures dans ColdFusion

Si cela n'est pas possible uniquement dans ColdFusion, est-il possible dans Java en quelque sorte (peut-être en utilisant Arrays.sort(Object[], Comparator))?

Répondre

12

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 ([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; 
} 
8

Voici quelque chose qui ressemble beaucoup à la StructSort() originale. Il supporte aussi l'argument pathToSubElement.

<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

+0

« clés » doit être var scope, je crois. –

+0

@Edward: Absolument, j'ai raté celui-là. Merci pour l'indice. – Tomalak

+1

La plupart des autres réponses dépendent de la fonction de rappel arraySort() (ajoutée dans CF10) ou de la fonction de membre sort() (ajoutée dans CF11). La réponse de Tomalak fonctionne au moins jusqu'à CF9, que je dois encore soutenir. Merci, Tomalak! –

1

Si vous ne souhaitez pas utiliser des méthodes personnalisées, Coldfusion a structSort méthode http://www.cfquickdocs.com/cf8/#StructSort. Oui, il trie la structure avec des structures imbriquées, BUT retourne le tableau, donc pourrait être utilisé pour obtenir le même résultat.

+3

Comment utiliser 'structSort()' pour trier un tableau de structures? – 10basetom

5

La solution acceptée (par CFLib.org) n'est PAS sûre. J'ai expérimenté avec ceci pour quelque chose que je devais faire au travail et j'ai constaté qu'il renvoie des résultats incorrects lors du tri numérique avec des flottants.

Par exemple, si j'ai ces struct: (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'); 
 

itérer sur le tableau triés et imprimer le nom & poids. Il ne sera pas dans le bon ordre, et c'est une limitation du mélange une clé arbitraire avec la valeur en cours de tri.

+4

Bonne information à partager, mais comme vous ne proposez pas de solution alternative, cela devrait faire l'objet d'un commentaire sur cette réponse. Vous pouvez placer l'exemple de code dans un fichier gist/pastebin/etc afin qu'il soit compatible. –

4

Vous pouvez utiliser le Underscore.cfc library pour accomplir ce que vous voulez:

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

_ = new Underscore(); 

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

Underscore.cfc vous permet de définir un comparateur personnalisé et les délégués à TableauTrie(). Vous pouvez l'utiliser pour trier des tableaux, des structures, des requêtes ou des listes de chaînes, mais il retourne toujours un tableau.

(Disclaimer: J'écrit Underscore.cfc)

2

Je voulais jeter mes deux cents ici. J'ai rencontré un cas où j'avais besoin de trier un tableau 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 struct comme premier argument, puis un tableau de struct indiquant l'ordre de tri, comme ceci:

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

Au sein de la fonction sortArrayOfStructsUsingQuery, construire une requête basée uniquement sur les touches je passe , puis triez cette requête. Ensuite, je boucle sur la requête, trouve l'élément de structure du tableau qui correspond aux données de la ligne de requête en cours, et ajoute cette structure au tableau que je remets.

Il est tout à fait possible qu'il y ait un trou béant dans ce code que mon test n'a pas découvert (il n'y a pas encore eu beaucoup de cas d'utilisation), mais c'est utile. J'espère que c'est utile, et s'il y a des trous flagrants, je suis heureux d'entendre parler d'eux.

(juste une note: J'utilise le champ « local » pour toutes les variables qui vont rester dans la fonction, et le « r » champ d'application pour tout ce que je l'intention de restituer, pour tout ce que ça vaut)

<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> 
1

En réalité, c'est encore plus facile avec le nouveau soutien de CF Closure.

Voici un exemple sur lequel j'ai travaillé aujourd'hui où je voulais trier un tableau de structures par une date stockée dans la structure. Je triais 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 le crédit totale que j'adaptai ce de Ray Camden de sur les fermetures de 2012.

+0

Ou 'function (a, b) {return (a.struct_date

+0

est-ce seulement dans CF 10? – Kip

+1

Les expressions et les fermetures de fonctions en ligne ont été ajoutées avec CF10 et Railo 4.0, tout comme l'ArraySort mis à jour. Vous avez toujours été en mesure de transmettre des UDF en tant qu'arguments, mais aucune des fonctions intégrées n'avait d'arguments qui acceptaient auparavant des fonctions. Ils n'autorisent pas (actuellement) les BIF, mais cela changera dans la prochaine version. –

2

est ici une UDF basée sur la réponse de Tomalak qui prend en charge également des objets personnalisés (par exemple, utilisé par certains Railo- CMS basés). 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> 
+0

Nice un. J'étais sur le point de regarder dans ma propre réponse, mais je suppose que je peux retarder cela pour un peu maintenant ... – Tomalak

2

Je n'ai pas les points de réputation pour commenter @ mikest34 post ci-dessus, mais @russ était exact que ce rappel ne fonctionne plus comme il a été expliqué.

Il était Adam Cameron qui a découvert que lors de l'utilisation TableauTrie avec un rappel, il ne nécessite plus une réponse Vrai/Faux mais plutôt:

-1, si le premier paramètre est « plus petit » que second paramètre
0, si le premier paramètre est égal à second paramètre
1, premier paramètre est "plus grand" que second paramètre

Ainsi, le rappel est correct:

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

Test et travailler dans CF2016