'The ArraySort callback is not sorting my array in the correct order

Using ColdFusion to sort an multi-dimensional array based on a "Price per sq. ft" field from high to low.

It has been in production and worked in testing, but a case has come up that has yielded strange, unsorted results. I also ran this on the CFDOCS site using their ArraySort code and got the same, incorrect sort results.

As you can see the results are not even really sorted.

Here's my code:

figures = [
   {name='carl',price='117.5'},
   {name='fen',price='116.4'},
   {name='joe',price='86.3'}
];

arraySort(figures, function (a, b){
   return compare(b.price, a.price);
});
writeDump(figures);

Results:

NAME    joe
PRICE   86.3

NAME    carl
PRICE   117.5

NAME    fen
PRICE   116.4

It should be sorted in this order: 117.5, 116.4, 86.3.

I believe it's sorting in a way that makes 86.3 appear greater than the rest because it starts with an 8? I also tried without the quotes and got the wrong results as well.

I ran this same code on cfdocs.org and got the same, wrong results.

Am I doing something incorrectly in my sort parameters or on the callback?

Thank you!



Solution 1:[1]

The ArraySort callback function "compares two elements of the array" at a time, and should return one of the following values:

  • -1 if the first element is less than the second
  • 0 if the first element is equal to the second
  • 1 if first element is greater than the second

While the compare() function does return 1,0 or -1, it compares the elements as strings, which isn't going to produce the expected order for numeric values. As Shawn suggested, adding some debugging code will show the results of each comparison:

arraySort(figures, function (a, b){

       local.num = compare(a.price, b.price);
       local.text = local.num == -1 ? " less than " : (local.num == 0 ? " equals " : " greater than");
       writeOutput("<br> "& a.price &" "& local.text &" "& b.price &" => "& local.num );

       return local.num ;
});

.. demonstrating that a string comparison doesn't produce the same results as a numeric comparison:

  • 116.4 less than 117.5 => -1
  • 86.3 greater than 116.4 => 1
  • 86.3 greater than 117.5 => 1
  • 1117.3 less than 117.5 => -1
  • 1117.3 less than 116.4 => -1

To sort the "price" values as numbers, use arithmetic operators instead of compare(). For descending order (high to low):

arraySort(figures, function (a, b){
   return (b.price < a.price) ? -1 : (b.price == a.price ? 0 : 1);
});

For ascending order (low to high), just swap the comparisons:

arraySort(figures, function (a, b){
   return (a.price < b.price) ? -1 : (a.price == b.price ? 0 : 1);
});

Runnable Example

Solution 2:[2]

As @SOS mentioned, it appears the compare() function compares numbers as strings even if you wrap them in a val(). But where he uses a ternary, you can also use the sgn() function instead.

For example...

// ascending order
arraySort(figures, function (a, b){
   return sgn(a.price - b.price);
});

// .. or for descending order
arraySort(figures, function (a, b){
   return sgn(b.price - a.price);
});

The sgn() function will always return a 1 for positive numbers, -1 for negative numbers, and 0 for zero...making it the perfect function (IMHO) for this use case.

Solution 3:[3]

Consider the compare() function. It can return -1, 0, or 1. Only 0 is considered false. To correct this you need

 arraySort(figures, function (a, b){
  return compare(b.price, a.price) == 1 ? 1 : 0;     
 });
 writeDump(figures);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 SOS
Solution 3 James A Mohler