2010-04-16 4 views
17

У меня есть массив структур в ColdFusion. Я хотел бы отсортировать этот массив на основе одного из атрибутов в структурах. Как я могу это достичь? Я нашел функцию StructSort, но она принимает структуру, и у меня есть массив.Как отсортировать массив структур в ColdFusion

Если это невозможно в ColdFusion, возможно ли это на Java (возможно, используя Arrays.sort(Object[], Comparator))?

ответ

12

Как обычно, CFLib.org имеет именно то, что вы хотите.

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

Вот то, что близко напоминает оригинальный StructSort(). Он также поддерживает аргумент 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> 

Использование/тест:

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

Результат:

ArrayOfStructSort Result

+0

"ключи" должен быть вар-Scoped, я считаю. –

+0

@Edward: Абсолютно, я пропустил это. Спасибо за подсказку. – Tomalak

+1

Многие другие ответы здесь зависят от функции обратного вызова arraySort() (добавлена ​​в функции CF10) или функции sort() (добавлено в CF11). Ответ Томалака работает, по крайней мере, на CF9, который я все еще должен поддерживать. Спасибо, Томалак! –

1

В случае, если вы не хотите использовать специальные методы, ColdFusion имеет structSort метод http://www.cfquickdocs.com/cf8/#StructSort. Да, он сортирует структуру с вложенными структурами, BUT возвращает массив, поэтому его можно использовать для достижения такого же результата.

+3

Как бы вы использовали 'structSort()' для сортировки массива структур? – 10basetom

5

Принятое решение (с CFLib.org) НЕ безопасно. Я экспериментировал с этим для чего-то, что мне нужно было сделать на работе, и обнаружил, что он возвращает неверные результаты при сортировке числовых чисел с помощью float.

Например, если у меня есть эти структуры: (псевдокод)


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

перебрать отсортированный массив и напечатать имя & веса. Он не будет в правильном порядке, и это ограничение смешивания произвольного ключа с сортируемым значением.

+4

Хорошая информация для обмена, но поскольку вы не предлагаете альтернативное решение, это должно быть в комментарии к этому ответу. Вы могли бы поместить образец кода в gist/pastebin/etc, чтобы он поместился. –

4

Вы можете использовать Underscore.cfc library, чтобы выполнить то, что вы хотите:

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

_ = new Underscore(); 

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

Underscore.cfc позволяет определить собственный компаратор и делегатов ArraySort(). Вы можете использовать его для сортировки массивов, структур, запросов или строковых списков, но он всегда возвращает массив.

(Отказ от ответственности: я написал Underscore.cfc)

2

Я хотел бросить свои два цента здесь. Я столкнулся с ситуацией, когда мне нужно было отсортировать массив структур, используя более одного ключа. Я закончил с помощью построенного запроса, чтобы выполнить сортировку. Функция принимает массив структур в качестве первого аргумента, а затем массив структур, указывающих порядок сортировки, например:

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

В функции sortArrayOfStructsUsingQuery, я построить запрос, основанный только на клавишах я передать в , затем отсортируйте этот запрос. Затем, перейдя по запросу, найдите элемент структуры из массива, который соответствует данным в текущей строке запроса, и добавьте эту структуру в массив, который я передаю.

Вполне возможно, что в этом коде есть зияющее отверстие, которое не было обнаружено моим тестированием (пока у меня не было много вариантов использования), но в случае, если это полезно кому-либо, вот оно. Надеюсь, это полезно, и если есть какие-то яркие дыры, я рад услышать о них.

(просто примечание: Я использую «локальную» область видимости для всех переменных, которые останутся в функции, и «г» сфера для чего я намерен передать обратно, за то, что стоит)

<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

На самом деле это еще проще с новой поддержкой CF Closure.

Вот пример, над которым я работал сегодня, где я хотел отсортировать массив структур по дате, хранящейся в структуре. Я сортировал в порядке убывания.

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

Я не могу взять полный кредит, как я приспособил это от Ray Camden на Closures с 2012

+0

Или 'function (a, b) {return (a.struct_date

+0

это только в CF 10? – Kip

+1

Функциональные выражения и замыкания были добавлены с CF10 и Railo 4.0, а также обновленный массив ArraySort. Вы всегда могли передавать UDF в качестве аргументов, но ни одна из встроенных функций не имела аргументов, которые ранее принимали функции. Они по-прежнему не позволяют (в настоящее время) разрешать BIF, но это, надеюсь, изменится в следующей версии. –

2

Вот UDF на основе ответа Томалак, который также поддерживает пользовательские объекты (к примеру, используется некоторыми Railo- основанные на CMS). Эта функция совместима с 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

Ницца. Я собирался заглянуть в свой собственный ответ, но я думаю, что могу немного отложить это сейчас ... – Tomalak

2

Я не имею очки репутации прокомментировать @ mikest34 пост выше, но @russ было правильно, что этот обратный вызов больше не работает так, как это было объяснено.

Это был Адам Камерон, который обнаружил, что при использовании ArraySort с обратным вызовом, он больше не требует True/False ответа, а скорее:

-1, если первый параметр «меньше», чем второй параметр
0, если первый параметр равен второму параметру
1, первый параметр «больше», чем второй параметр

Так правильный обратный вызов:

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

Тестирование и работа в CF2016

Смежные вопросы