2010-04-16 20 views
17

Tengo una matriz de estructuras en ColdFusion. Me gustaría ordenar esta matriz en función de uno de los atributos en las estructuras. ¿Cómo puedo conseguir esto? Encontré la función StructSort, pero necesita una estructura y tengo una matriz.Cómo ordenar una matriz de estructuras en ColdFusion

Si esto no es posible solo en ColdFusion, ¿es posible en Java de alguna manera (tal vez usando Arrays.sort(Object[], Comparator))?

Respuesta

12

Como siempre, CFLib.org tiene exactamente lo que desea.

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

Aquí es algo que se parece mucho al original StructSort(). También es compatible con el argumento 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> 

Resultado:

ArrayOfStructSort Result

+0

"claves" es necesario que haya var con ámbito, creo. –

+0

@Edward: Absolutamente, me he perdido esa. Gracias por la pista. – Tomalak

+1

Muchas de las otras respuestas aquí dependen de la función de devolución de llamada arraySort() (agregada en CF10) o función de miembro sort() (agregada en CF11). La respuesta de Tomalak funciona al menos hasta CF9, que todavía tengo que admitir. ¡Gracias, Tomalak! –

1

En caso de que no desee utilizar métodos personalizados, ColdFusion tiene structSort método http://www.cfquickdocs.com/cf8/#StructSort. Sí, ordena la estructura con estructuras anidadas, PERO devuelve la matriz, por lo que podría usarse para lograr el mismo resultado.

+3

¿Cómo usarías 'structSort()' para ordenar una matriz de estructuras? – 10basetom

5

La solución aceptada (de CFLib.org) NO es segura. Experimenté esto para algo que tenía que hacer en el trabajo y descubrí que devuelve resultados incorrectos al ordenar numéricos con flotantes.

Por ejemplo si tienen estas estructuras: (pseudocódigo)


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

iterar sobre la matriz ordenada e imprimir el nombre & peso. No estará en el orden correcto, y esto es una limitación para mezclar una clave arbitraria con el valor ordenado.

+4

Buena información para compartir, pero como no está proponiendo una solución alternativa, debería incluir un comentario sobre esa respuesta. Podrías poner la muestra del código en un gist/pastebin/etc para que encaje. –

4

Puede utilizar el Underscore.cfc library para lograr lo que quiere:

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

_ = new Underscore(); 

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

Underscore.cfc le permite definir un comparador y los delegados a medida para arraySort(). Puede usarlo para ordenar matrices, estructuras, consultas o listas de cadenas, pero siempre devuelve una matriz.

(Negación: Escribí Underscore.cfc)

2

quería tirar mis dos centavos aquí. Me encontré con un caso en el que necesitaba ordenar una matriz de estructuras usando más de una clave. Terminé usando una consulta construida para hacer mi clasificación. La función toma el conjunto de estructuras como primer argumento, y luego una serie de estructuras que indican el orden de clasificación, así:

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

Dentro de la función sortArrayOfStructsUsingQuery, construyo una consulta basada sólo en las claves que pase en , luego ordena esa consulta Luego, recorro la consulta, encuentro el elemento de estructura de la matriz que coincide con los datos en la fila de consulta actual y agrego esa estructura a la matriz que devuelvo.

Es muy posible que haya un agujero en este código que mis pruebas no han descubierto (no ha habido muchos casos de uso para mí todavía), pero en caso de que sea útil para cualquiera, aquí está. Espero que sea útil, y si hay algún agujero evidente, me alegra saber de ellos.

(sólo una nota: Yo uso el ámbito "local" para todas las variables que se quedará en la función, y la "r" margen para nada pretendo a devolver, por lo que vale la pena)

<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 realidad, es aún más fácil con el nuevo soporte CF Closure.

Aquí hay un ejemplo en el que trabajé hoy en el que quería ordenar una matriz de estructuras por una fecha almacenada en la estructura. Estaba ordenando en orden descendente.

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

no puedo tomar crédito total como Adapté esto desde Ray Camden sobre el cierre de 2012.

+0

O función (a, b) {return (a.struct_date

+0

es esto solo en CF 10? – Kip

+1

Se agregaron expresiones y cierres de funciones en línea con CF10 y Railo 4.0, al igual que ArraySort actualizado. Siempre ha podido pasar UDF como argumentos, pero ninguna de las funciones incorporadas tenía args que aceptaron funciones previamente. Aún no permiten (actualmente) los BIF, pero es de esperar que cambien en la próxima versión. –

2

Aquí es una UDF basado en la respuesta de Tomalak que también es compatible con los objetos personalizados (por ejemplo, utilizado por algunos Railo- CMS basados). Esta función es compatible 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

Bueno. Estaba a punto de buscar mi propia respuesta, pero creo que puedo retrasar eso por un momento ... – Tomalak

2

no tengo los puntos de reputación para comentar @ mikest34 post anterior, pero @russ era correcto que esta devolución de llamada ya no funciona de la manera que se ha explicado.

Fue Adam Cameron quien descubrió que cuando se utiliza arraySort con una devolución de llamada, ya no requiere una respuesta verdadero/falso, sino más bien:

-1, si el primer parámetro es "más pequeño" que el segundo parámetro
0, si el primer parámetro es igual al segundo parámetro
1, primer parámetro es "más grande" que el segundo parámetro

Así que la devolución de llamada correcta es:

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

Pruebas y trabajar en CF2016

Cuestiones relacionadas