CURRENT PROJECTS
loading
>>> naturalSort.insensitive = true; >>> ['a', 'B'].sort(naturalSort); ["a", "B"]As opposed to the default case-sensitive sorting:
>>> ['a', 'B'].sort(naturalSort); ["B", "a"]I would like to come up with a more elegant way to flag case sensitivity - I was thinking of curry/closure/etc. idea to something of the degree
[1,2].sort(naturalSort(true))
to sort case-insensitively, but more on that later.
Date.parse('1')
in Chrome Beta returns a valid UTC time value while in other browsers it returns a NaN. Earlier versions of my naturalSort() relied on Date.parse() to detect dates in any/all comparisons which was a bad assumption on my part because of how loose the ECMA spec is on the details of what Date.parse() is able to parse into a date. Quoting the ECMA-262 spec in section 15.9.4.2, "The function first attempts to parse the format of the String according to the rules called out in Date Time String Format (15.9.1.15). The function first attempts to parse the format of the String according to the rules called out in Date Time String Format (15.9.1.15). If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats." it is safe to assume each implementation will attempt to parse an ISO8601 date string and then fall back on basically anything else - which is the case in the new Chrome Beta. I updated a new Date regexp to aid in detection of dates instead of assuming Date.parse() will be able to perfectly detect them. The regexp loosely attempts to find ISO8601, RFC1123, .toString(), .toUTCString(), .toLocaleDateString(), etc date formats which should work in the majority of date sorting use-cases.
/* * Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license * Author: Jim Palmer (based on chunking idea from Dave Koelle) */ function naturalSort (a, b) { var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, sre = /(^[ ]*|[ ]*$)/g, dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, hre = /^0x[0-9a-f]+$/i, ore = /^0/, i = function(s) { return naturalSort.insensitive && (''+s).toLowerCase() || ''+s }, // convert all to strings strip whitespace x = i(a).replace(sre, '') || '', y = i(b).replace(sre, '') || '', // chunk/tokenize xN = x.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'), yN = y.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'), // numeric, hex or date detection xD = parseInt(x.match(hre)) || (xN.length != 1 && x.match(dre) && Date.parse(x)), yD = parseInt(y.match(hre)) || xD && y.match(dre) && Date.parse(y) || null, oFxNcL, oFyNcL; // first try and sort Hex codes or Dates if (yD) if ( xD < yD ) return -1; else if ( xD > yD ) return 1; // natural sorting through split numeric strings and default strings for(var cLoc=0, numS=Math.max(xN.length, yN.length); cLoc < numS; cLoc++) { // find floats not starting with '0', string or 0 if not defined (Clint Priest) oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc]) || xN[cLoc] || 0; oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc]) || yN[cLoc] || 0; // handle numeric vs string comparison - number < string - (Kyle Adams) if (isNaN(oFxNcL) !== isNaN(oFyNcL)) { return (isNaN(oFxNcL)) ? 1 : -1; } // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2' else if (typeof oFxNcL !== typeof oFyNcL) { oFxNcL += ''; oFyNcL += ''; } if (oFxNcL < oFyNcL) return -1; if (oFxNcL > oFyNcL) return 1; } return 0; }
new Date('10/10/2005')
or new Date('Tue Sep 09 2008 20:32:28 GMT-0700 (Pacific Daylight Time)')
, can work. The value cannot contain any other character that might throw the Date constructor off, i.e. a '10/10/2005 original' will not work.
// Simple numerics >>> ['10',9,2,'1','4'].sort(naturalSort) ['1',2,'4',9,'10'] // Floats >>> ['10.0401',10.022,10.042,'10.021999'].sort(naturalSort) ['10.021999',10.022,'10.0401',10.042] // Float & decimal notation >>> ['10.04f','10.039F','10.038d','10.037D'].sort(naturalSort) ['10.037D','10.038d','10.039F','10.04f'] // Scientific notation >>> ['1.528535047e5','1.528535047e7','1.528535047e3'].sort(naturalSort) ['1.528535047e3','1.528535047e5','1.528535047e7'] // IP addresses >>> ['192.168.0.100','192.168.0.1','192.168.1.1'].sort(naturalSort) ['192.168.0.1','192.168.0.100','192.168.1.1'] // Filenames >>> ['car.mov','01alpha.sgi','001alpha.sgi','my.string_41299.tif'].sort(naturalSort) ['001alpha.sgi','01alpha.sgi','car.mov','my.string_41299.tif' // Dates >>> ['10/12/2008','10/11/2008','10/11/2007','10/12/2007'].sort(naturalSort) ['10/11/2007', '10/12/2007', '10/11/2008', '10/12/2008'] // Money >>> ['$10002.00','$10001.02','$10001.01'].sort(naturalSort) ['$10001.01','$10001.02','$10002.00'] // Movie Titles >>> ['1 Title - The Big Lebowski','1 Title - Gattaca','1 Title - Last Picture Show'].sort(naturalSort) ['1 Title - Gattaca','1 Title - Last Picture Show','1 Title - The Big Lebowski'] // By default - case-sensitive sorting >>> ['a', 'B'].sort(naturalSort); ['B', 'a'] // To enable case-insensitive sorting >>> naturalSort.insensitive = true; >>> ['a', 'B'].sort(naturalSort); ['a', 'B']