Well before I had this site, I created a program which needed to order file names such as the following:


Episode 20 - There's More Than One of Everything.mp4
Episode 4 - The Arrival.mp4
Episode 7 - In Which We Meet Mr. Jones.mp4
Episode 15 - Inner Child.mp4
Episode 6 - The Cure.mp4
Episode 3 - The Ghost Network.mp4

Unfortunately, if you just use the normal {Array}.sort() without a callback function, the following will be the resulting order of the file names:


Episode 15 - Inner Child.mp4
Episode 20 - There's More Than One of Everything.mp4
Episode 3 - The Ghost Network.mp4
Episode 4 - The Arrival.mp4
Episode 6 - The Cure.mp4
Episode 7 - In Which We Meet Mr. Jones.mp4

As you can see, the 15th episode comes before the 3rd episode after being sorted but in most cases we would want the order to be as follows:


Episode 3 - The Ghost Network.mp4
Episode 4 - The Arrival.mp4
Episode 6 - The Cure.mp4
Episode 7 - In Which We Meet Mr. Jones.mp4
Episode 15 - Inner Child.mp4
Episode 20 - There's More Than One of Everything.mp4

For this reason, I wrote the cmpStringsWithNumbers function which takes two strings and compares them by separating the substrings with numbers from the substrings without numbers:


(function() {
  // Regular expression to separate the digit string from the non-digit strings.
  var reParts = /\d+|\D+/g;

  // Regular expression to test if the string has a digit.
  var reDigit = /\d/;

  // Add cmpStringsWithNumbers to the global namespace.  This function takes to
  // strings and compares them, returning -1 if `a` comes before `b`, 0 if `a`
  // and `b` are equal, and 1 if `a` comes after `b`.
  cmpStringsWithNumbers = function(a, b) {
    // Get rid of casing issues.
    a = a.toUpperCase();
    b = b.toUpperCase();

    // Separates the strings into substrings that have only digits and those
    // that have no digits.
    var aParts = a.match(reParts);
    var bParts = b.match(reParts);

    // Used to determine if aPart and bPart are digits.
    var isDigitPart;

    // If `a` and `b` are strings with substring parts that match...
    if(aParts && bParts &&
        (isDigitPart = reDigit.test(aParts[0])) == reDigit.test(bParts[0])) {
      // Loop through each substring part to compare the overall strings.
      var len = Math.min(aParts.length, bParts.length);
      for(var i = 0; i < len; i++) {
        var aPart = aParts[i];
        var bPart = bParts[i];

        // If comparing digits, convert them to numbers (assuming base 10).
        if(isDigitPart) {
          aPart = parseInt(aPart, 10);
          bPart = parseInt(bPart, 10);
        }

        // If the substrings aren't equal, return either -1 or 1.
        if(aPart != bPart) {
          return aPart < bPart ? -1 : 1;
        }

        // Toggle the value of isDigitPart since the parts will alternate.
        isDigitPart = !isDigitPart;
      }
    }

    // Use normal comparison.
    return (a >= b) - (a <= b);
  };
})();

Here is an example of how one could use the above function:


var fileNames = [
  "Episode 20 - There's More Than One of Everything.mp4",
  "Episode 4 - The Arrival.mp4",
  "Episode 7 - In Which We Meet Mr. Jones.mp4",
  "Episode 15 - Inner Child.mp4",
  "Episode 6 - The Cure.mp4",
  "Episode 3 - The Ghost Network.mp4"
].sort(cmpStringsWithNumbers);

alert(fileNames.join('\n'));

After the above two code blogs run, the following would be alerted to the user:


Episode 3 - The Ghost Network.mp4
Episode 4 - The Arrival.mp4
Episode 6 - The Cure.mp4
Episode 7 - In Which We Meet Mr. Jones.mp4
Episode 15 - Inner Child.mp4
Episode 20 - There's More Than One of Everything.mp4

One thing that is important to note is that this comparison function is case-insensitive. This function is pretty well documented so if you have a need for it in your JScript, HTA 8) , or JavaScript file, feel free to use it.


2 Comments

ildar · April 10, 2013 at 6:29 PM

Hi Chris!

Be careful with [].sort method. Maybe, the better way is to implement your own sorting method. Try in your bed the following simple example:

[3, 1, 2].sort(function(a, b)
{
alert([a, b]);
return a – b;
};

You can say that there is nothing to be suspected. But! Each time when the sorter function is called it performs some calculation over passed arguments “a” and “b”. It means that each time when “cmpStringsWithNumbers” is called it performs heavy calculations over the same values again and again.

Erick · June 12, 2019 at 4:04 PM

Hi, thank you very much I shall implement it for backbone.js collections

Leave a Reply

Your email address will not be published. Required fields are marked *