Как отсортировать массив структур в ColdFusion

17

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

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

    
задан Kip 16.04.2010 в 16:45
источник

9 ответов

12

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

Ссылка

/**
* 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;
}
    
ответ дан ale 16.04.2010 в 16:52
9

Вот что-то, что очень напоминает оригинальный 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>

Результат:

    
ответ дан Tomalak 16.04.2010 в 17:04
  • «ключи» должны быть расширены. –  Edward M Smith 16.04.2010 в 17:49
  • @Edward: Абсолютно, я пропустил это. Спасибо за подсказку. –  Tomalak 16.04.2010 в 17:53
  • Многие другие ответы здесь зависят от функции обратного вызова arraySort () (добавляется в CF10) или функции члена sort () (добавляется в CF11). Ответ Томалака работает, по крайней мере, на CF9, который мне все еще нужно поддерживать. Спасибо, Томалак! –  Kevin Morris 24.01.2017 в 17:01
5

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

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


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

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

    
ответ дан jpswain 31.07.2010 в 01:21
  • Хорошая информация для обмена, но поскольку вы не предлагаете альтернативное решение, это должно быть в комментарии к этому ответу. Вы могли бы поместить образец кода в gist / pastebin / etc, чтобы он поместился. –  Adam Tuttle 17.01.2013 в 22:02
4

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

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

_ = new Underscore();

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

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

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

    
ответ дан Russ 06.07.2012 в 05:59
2

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

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

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

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

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

<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>
    
ответ дан hairbo 22.10.2012 в 21:42
2

Вот UDF, основанный на ответе Томалака, который также поддерживает пользовательские объекты (например, используемые некоторыми CMS на основе Railo). Эта функция совместима с 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>
    
ответ дан thdoan 16.10.2015 в 12:59
  • Хороший. Я собирался заглянуть в свой собственный ответ, но я думаю, что могу немного отложить это сейчас ... –  Tomalak 16.10.2015 в 13:05
2

Это действительно проще с новой поддержкой CF Closure.

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

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

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

    
ответ дан mikest34 07.10.2013 в 23:37
  • Или функция (a, b) {return (a.struct_date <b.struct_date); } –  Peter Boughton 07.10.2013 в 23:45
  • это только в CF 10? –  Kip 08.10.2013 в 00:09
  • Встроенные функциональные выражения и замыкания были добавлены с помощью CF10 и Railo 4.0, а также обновленный ArraySort. Вы всегда могли передавать UDF в качестве аргументов, но ни одна из встроенных функций не имела аргументов, которые ранее принимали функции. Они по-прежнему не позволяют (в настоящее время) разрешать BIF, но это, надеюсь, изменится в следующей версии. –  Peter Boughton 08.10.2013 в 10:46
  • Спасибо, Питер. Я также понял, что после публикации есть более простые подходы. –  mikest34 15.10.2013 в 22:01
  • Остерегайтесь того, что реализация arraySort () может быть изменена: cfmlblog.adamcameron.me/2013/07/... –  Russ 06.11.2013 в 03:49
2

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

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

  

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

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

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

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

    
ответ дан DaveB 08.12.2017 в 19:21
1

Если вы не хотите использовать пользовательские методы, Coldfusion имеет метод structSort Ссылка . Да, он сортирует структуру с вложенными структурами, BUT возвращает массив, поэтому его можно использовать для достижения того же результата.

    
ответ дан zarko.susnjar 16.04.2010 в 19:48
  • Как бы вы использовали structSort () для сортировки массива структур? –  thdoan 16.10.2015 в 09:19