Understanding Sort () compareFunction

I work with an e-commerce platform that does not have the ability to reorder the fields of our product attributes. This really sucks, because to insert a new option, you pretty much need to delete all existing ones and start all over again. Instead, I am trying to do this on the client side. Here I work (this one for shoe size):

  • 9 EE
  • 9 1/2 EE
  • 10 EE
  • 10 1/2 EE
  • 11 EE
  • 11 1/2 EE
  • 9 EEEE
  • 9 1/2 D
  • 9 1/2 EEEE
  • 10 EEEE
  • 10 1/2 EEEE
  • 11 EEEE
  • 9 D
  • 11 1/2 EEEE

This is actually the text of some <option> in the form. The format of the XYZ values ​​is where:

  • X is an integer
  • Y - string "1/2" and may be absent
  • Z is a letter code that is either "D", "E", "EEE", or "EEEE" and may be absent.

The required order above will be as follows:

  • 9 D
  • 9 1/2 D
  • 9 EE
  • 9 1/2 EE
  • 9 EEEE
  • 9 1/2 EEEE
  • 10 EE
  • 10 1/2 EE
  • 10 EEEE
  • 10 1/2 EEEE
  • 11 EE
  • 11 1/2 EE
  • 11 EEEE
  • 11 1/2 EEEE

I learned a little about the javascript sort() function, but could not fully understand how the comparison function works, which you can pass to it. I still have this:

 <select> <option>9 EE</option> <option>9 1/2 EE</option> <option>10 EE</option> <option>10 1/2 EE</option> <option>11 EE</option> <option>11 1/2 EE</option> <option>9 EEEE</option> <option>9 1/2 D</option> <option>9 1/2 EEEE</option> <option>10 EEEE</option> <option>10 1/2 EEEE</option> <option>11 EEEE</option> <option>9 D</option> <option>11 1/2 EEEE</option> </select> 

I started with the code taken from this answer: stack overflow

 $("select").html($("option").sort(function (a, b) { return a.text == b.text ? 0 : a.text < b.text ? -1 : 1 })); 

What types of elements like this (do not work even for the first criteria):

  • 10 1/2 EE
  • 10 1/2 EEEE
  • 10 EE
  • 10 EEEE
  • 11 1/2 EE
  • 11 1/2 EEEE
  • 11 EE
  • 11 EEEE
  • 9 1/2 D
  • 9 1/2 EE
  • 9 1/2 EEEE
  • 9 D
  • 9 EE
  • 9 EEEE

I see that in javascript '11' > '9' returns false , which in no way makes sense to me.

MDN describes the argument of the comparison function as such, and I kind of get it:

 function compare(a, b) { if (a is less than b by some ordering criterion) return -1; if (a is greater than b by the ordering criterion) return 1; // a must be equal to b return 0; } 

... but I have no clue how to adapt this to fit my requirements. I tried a few things, but I just feel like shooting in the dark. I tried to show that I spent some time trying to understand this problem. I am interested to know more, but now I just want to solve this problem.

http://jsfiddle.net/DnwJ6/ Any hints?

+7
javascript jquery
source share
6 answers

Note this:

 $("select").html($("option").sort(function (a, b) { var regex = /(\d+)((?: 1\/2)? )([DE]+)/; var abreakdown = a.text.match(regex), bbreakdown = b.text.match(regex); if (parseInt(abreakdown[1]) === parseInt(bbreakdown[1])) { if (abreakdown[3] === bbreakdown[3]) { return (abreakdown[2] === bbreakdown[2] ? 0 : (abreakdown[2] < bbreakdown[2] ? -1 : 1)); } else { return abreakdown[3] < bbreakdown[3] ? -1 : 1; } } else { return parseInt(abreakdown[1]) - parseInt(bbreakdown[1]); } })); 

It uses regex to break pieces, and then perform a comparison based on each component.

Demo script.

+1
source share

Put the values ​​in the array in the order you want:

 var shoeSizes = [ '9 D','9 1/2 D','9 EE','9 1/2 EE', ...] 

Use Array.protoype.indexOf ( using padding for older browsers ) to get the index of matching text in the array. Use an index to compare, for example:

 function(a,b) { return shoeSizes.indexOf(a) - shoeSizes.indexOf(b); } 

If you need to deal with values ​​not in the array, check the value returned by indexOf and replace the default value if it is -1.

Alternatively, you can make the sizes of the names of property values ​​in the object and assign a specific value:

 var shoeSizes = { '9 D': 5, '9 1/2 D': 10, '9 EE': 15, '9 1/2 EE': 20, ...}; 

Then use the value in comparison:

 function(a,b) { return shoeSizes[a] - shoeSizes[b]; } 

Or to specify default values:

 function(a,b) { return (a in shoeSizes? shoeSizes[a] : 1000) - (b in shoeSizes? shoeSizes[b] : 1000); } 
+2
source share

Since you formatted the text, one way is to normalize the text elements before matching them.

This solution may not be as optimal, but it will do the job.

 $("select").html($("option").sort(function (a, b) { return nomalize(a.text) < nomalize(b.text) ? -1 : 1; })); function nomalize(val){ var parts = val.split(' '), op = ''; op = parts[0].length == 1 ? '0' + parts[0] : parts[0]; if(parts.length > 1){ if(/[az]/i.test(parts[1])){ op += '0/0' + parts[1]; } else { op += parts[1] } } op += parts.length > 2 ? parts[2] : ''; return op; } 

Demo: Fiddle

If someone can offer any solution to optimize it, it will be great

+2
source share

The first thing you need to understand is that 12 really less than 9 if you are sorting text elements, not numbers. This is because <1> <2> less than <9> <nothing> , because 1 and 9 are the "primary keys" in this case.

The second problem you are facing is the length (9 1/2) - primary key or width (EE). I would suspect that the former will make more sense, so continue on that basis.

Having decided that the best option is to provide a sort function for the call, which turns each row into a numeric value, and then compares that value. For example:

  • Get the first field with a separator (9) and set the value for it.
  • If the next field exists and 1/2 , add 0.5 to this value.
  • If the last field exists (alpha unit), just convert it to some value less than 0.5 and add it (for example, a β†’ 0.01, B β†’ 0.02, ..., EEEE β†’ 0.08, etc.).

The latter depends on the relative ordering of the width, I chose a typical American system.

As a result, you have a value that dictates the correct ordering, and your sort function can then simply perform a numerical comparison. The following is an example:

 function xlat(s) { var s2 = s.split(" "); var n = parseInt(s2[0]); if (s2.length == 1) { return n; } var last = s2[1]; if (last == '1/2') { n = n + 0.5; if (s2.length == 2) { return n; } last = s2[2]; } var widths = ['A','B','C','D','E','EE','EEE','EEEE','F','G']; n = n + widths.indexOf(last) / 100; return n; } $("select").html($("option").sort(function (a, b) { var na = xlat(a.text); var nb = xlat(b.text); return na == nb ? 0 : na < nb ? -1 : 1; })); 

The xlat functions are important xlat . It first splits the size into an array of 1, 2, or 3 elements and gets a numeric value for the first. If there is no second and third, this value is returned (processes bare sizes such as 9 or 13 ).

Otherwise, it decides whether the length of the half increment is - this is determined if the second field is 1/2 . At this point, it also determines if there is a width and returns the size.

Once this happened, we have the size (integer or half), and the last variable contains the width. Then we simply add a value based on this size position in the array, appropriately modified (divided by 100) so that it does not affect the main key.

Using this code with your own, you get (as expected):

 9 D 9 EE 9 EEEE 9 1/2 D 9 1/2 EE 9 1/2 EEEE 10 EE 10 EEEE 10 1/2 EE 10 1/2 EEEE 11 EE 11 EEEE 11 1/2 EE 11 1/2 EEEE 
+1
source share

First you need to determine the correct sort key, with which you can make reasonable comparisons; The following function uses a regular expression to extract useful bits of information:

 function sortkey(val) { var matches = val.match(/^(\d+)( 1\/2)? (\w+)$/), number = +matches[1]; if (matches[2]) { number += 0.5; // add "1/2" } return [number, matches[3]]; } 

The first match is a number; if a second match is available, 0.5 added. After that, the last match is added as a secondary sort key. The return value looks something like this:

 [9.5, 'EE'] 

You can then use this structure for your comparison function:

 function compareFunc(a, b) { var sa = sortkey(a.text), sb = sortkey(b.text); if (sa[0] == sb[0]) { return sa[1] < sb[1] ? -1 : 1; } else { return sa[0] < sb[0] ? -1 : 1; } } 

Applies to your specific code:

 var $sorted = $('select > option').sort(compareFunc); $('select').html($sorted); 

Demo

+1
source share

I see that you already have a working solution, but just for comparison (pun intended), here is one of many other ways to do this ( fiddle ):

 // Make a shoe size sortable as text function sortableSize( text ) { // Split the size into parts separated by spaces var parts = text.split( ' ' ); // The first part is the size number; // make sure it is two digits (09,10,etc.) if( parts[0].length == 1 ) parts[0] = '0' + parts[0]; // If it wasn't a 1/2 size, make it 0/2 if( parts.length == 2 ) parts.splice( 1, 0, '0/2' ); // So '9 EE' becomes '09 0/2 EE' return parts.join( ' ' ); } var $options = $('#sizes option'); $options = $options.sort( function( a, b ) { a = sortableSize( a.text ); b = sortableSize( b.text ); return a < b ? -1 : a > b ? 1 : 0; }); $('#sizes').html( $options ); 

This method creates a textual representation of each shoe size, which is directly sorted as text. It is then sorted using these text representations.

+1
source share

All Articles