2010-04-16 19 views
17

Ho una matrice di strutture in ColdFusion. Mi piacerebbe ordinare questo array in base a uno degli attributi nelle strutture. Come posso raggiungere questo obiettivo? Ho trovato la funzione StructSort, ma prende una struttura e ho una matrice.Come ordinare una matrice di strutture in ColdFusion

Se questo non è possibile esclusivamente in ColdFusion, è possibile in Java in qualche modo (forse utilizzando Arrays.sort(Object[], Comparator))?

risposta

12

Come al solito, CFLib.org ha esattamente quello che vuoi.

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

Qui è qualcosa che ricorda da vicino l'originale StructSort(). Supporta anche l'argomento 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> 

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

Risultato:

ArrayOfStructSort Result

+0

"chiavi" deve essere var con ambito, credo. –

+0

@Edward: Assolutamente, ho perso quello. Grazie per il suggerimento. – Tomalak

+1

Molte delle altre risposte dipendono dalla funzione di callback arraySort() (aggiunta in CF10) o sort() (aggiunta in CF11). La risposta di Tomalak funziona almeno su CF9, che devo ancora supportare. Grazie, Tomalak! –

1

Nel caso in cui non si desidera utilizzare metodi personalizzati, Coldfusion ha structSort metodo http://www.cfquickdocs.com/cf8/#StructSort. Sì, ordina la struttura con strutture nidificate, BUT restituisce array, quindi potrebbe essere utilizzato per ottenere lo stesso risultato.

+3

Come si usa 'structSort()' per ordinare una matrice di strutture? – 10basetom

5

La soluzione accettata (da CFLib.org) NON è sicura. Ho sperimentato questo per qualcosa che dovevo fare al lavoro e ho scoperto che restituisce risultati errati durante l'ordinamento numerico con float.

Per esempio, se ho questi le strutture: (pseudo)


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

iterare l'array ordinato e stampare il nome & peso. Non sarà nell'ordine corretto, e questa è una limitazione nel mixare una chiave arbitraria con il valore che viene ordinato.

+4

Buone informazioni da condividere, ma poiché non stai proponendo una soluzione alternativa, questo dovrebbe essere in un commento su quella risposta. È possibile inserire l'esempio di codice in un gist/pastebin/etc in modo che si adatti. –

4

È possibile utilizzare il Underscore.cfc library per realizzare ciò che si vuole:

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

_ = new Underscore(); 

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

Underscore.cfc consente di definire un comparatore personalizzato e delega al arraySort(). È possibile utilizzarlo per ordinare array, strutture, query o elenchi di stringhe, ma restituisce sempre un array.

(Disclaimer: ho scritto Underscore.cfc)

2

volevo buttare i miei due centesimi in qui. Mi sono imbattuto in un caso in cui avevo bisogno di ordinare una matrice di strutture usando più di una chiave. Sono finito usando una query costruita per fare il mio smistamento. La funzione prende l'array di struct come primo argomento, e quindi un array di struct indicano l'ordinamento, in questo modo:

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

All'interno della funzione sortArrayOfStructsUsingQuery, costruisco una query basata solo sulle chiavi mi passa , quindi ordina quella query. Quindi, eseguo il loop sulla query, trova l'elemento struttura dall'array che corrisponde ai dati nella riga di query corrente e aggiungo la struttura all'array che restituisco.

È completamente possibile che ci sia un buco aperto in questo codice che il mio test non ha scoperto (non ci sono stati ancora molti casi d'uso per me), ma nel caso sia utile a qualcuno, eccolo qui. Spero sia utile, e se ci sono buchi lampanti, sono felice di sentir parlare di loro.

(solo una nota: Io uso l'ambito "locale" per tutte le variabili che resteranno nella funzione, e la "r" portata per qualsiasi cosa ho intenzione di restituire, per quel che vale)

<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

In realtà è ancora più semplice con il nuovo supporto CF Closure.

Ecco un esempio su cui ho lavorato oggi in cui volevo ordinare una matrice di strutture per una data memorizzata nella struttura. Stavo ordinando in ordine decrescente.

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

Non posso prendere credito totale, come mi sono adattato questo da Ray Camden sulle chiusure a partire dal 2012.

+0

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

+0

è presente solo in CF 10? – Kip

+1

Le espressioni e le chiusure di funzioni inline sono state aggiunte con CF10 e Railo 4.0, come lo è stato l'ArraySort aggiornato. Sei sempre stato in grado di passare UDF come argomenti, ma nessuna delle funzioni built-in aveva argomenti che accettavano le funzioni in precedenza. Ancora non permettono (attualmente) i BIF, ma spero che cambino nella prossima versione. –

2

Ecco un UDF in base alla risposta del Tomalak che supporta anche oggetti personalizzati (ad esempio, utilizzato da alcuni Railo- CMS basati). Questa funzione è compatibile con 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

Bello. Stavo per esaminare la mia risposta, ma suppongo di poterlo rimandare per un po 'ora ... – Tomalak

2

Non ho i punti reputazione di commentare @ mikest34 post di cui sopra, ma @russ era corretto che questo callback non funziona più il modo in cui è stato spiegato.

E 'stato Adam Cameron che ha scoperto che quando si utilizza arraySort con una richiamata, non è più necessario un vero/falso risposta, ma piuttosto:

-1, se il primo parametro è "più piccolo" di secondo parametro
0, se il primo parametro è uguale al secondo parametro
1, primo parametro è "più grande" di secondo parametro

Così il callback corretto è:

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

test e di lavoro in CF2016

Problemi correlati