I have a strange array that I need to sort in a certain way, and I'm not sure if it's doable without a lot of manual labor.
Consider the array: [ '1a', '2a', '2aa', '5-6', '1', '2', '3', '20' , '7', '8']
It's an array of strings. Sorted properly, it would look like:
[ '1', '1a', '2', '2a', '2aa', '3', '5-6', '7', '8', '20' ]
Think of it like an outline numbering system of sorts.
How can I sort in that order?
Here's a jsfiddle showing how a regular sort treats it with its default lexicographical behavior (it's actually pretty close to what it needs to be):
I am working in node.js and have underscore.js at my disposal.
Ok, here's my attempt at solving this problem. The code is pretty verbose right now, but I'm looking at how to shrink it and will update my answer as I go. This should solve the problem enough to get you moving though. I'll be posting my code as a node module, since that's your env.
The gist of it is that it breaks the items down into separate parts that can be individually sorted. It loops each of those arrays in parallel, and once it finds a mismatch it will run a basic sort comparison.
// regex patterns to determine how to "parse" an input item
var patterns = [
/^([a-zA-Z]+)$/, // only letters
/^(\d+)$/, // only contains an integer
/^(\d+)\-\d+$/, // contains 2 integers separated by a hyphen
/^(\d+)([a-z]+)?\-(\d+)([a-z]+)?$/, // contains a 2 integer/letter combos separated by a hyphen
/^(\d+)([a-z]+)$/ // contains an integer followed by letters
];
function itemComparator(item) {
var len = patterns.length,
matches, x, regex;
for (x = 0; x < len; x++) {
regex = patterns[x];
if (regex.test(item)) {
matches = item.match(regex).slice(1);
if (/^\d+$/.test(matches[0])) {
matches[0] = parseInt(matches[0], 10);
}
return matches;
}
}
throw new Error("could not parse item for comparison: " + item);
}
module.exports = function (a, b) {
var compA = itemComparator(a),
compB = itemComparator(b),
x, len, tmpA, tmpB, typeA, typeB; // tmp vars
// find the largest size, so we don't miss anything
len = Math.max(compA.length, compB.length);
// loop each comp arr in parallel
for (x = 0; x < len; x += 1) {
// store for speed
tmpA = compA[x];
tmpB = compB[x];
typeA = typeof tmpA;
typeB = typeof tmpB;
// if the elements are not equal
if (tmpA !== tmpB) {
// then do the comparison, and stop the loop
if (typeA === typeB) {
return tmpA < tmpB ? -1 : 1;
} else if (typeA === "undefined") {
return -1;
} else if (typeB === "undefined") {
return 1;
} else if (typeA === "string") {
return -1;
} else if (typeB === "string") {
return 1;
} else {
console.warn("unexpected condition for %s (%s) and %s (%s)", tmpA, typeA, tmpB, typeB);
return 0;
}
}
}
};
var sorter = require("./sorter"),
arr = [
'1a', 'aa', '2a', '2aa', '5-6', '1', '2', '3', 'DBA', 'bb',
'20', '2b', '7', '8', '125a', '33a-35', 'ABC',
'3aaa-4c', '3aaa-52d', 'AA', 'c', '5dd', 'aa'
];
console.log(arr.sort(sorter));
// [ 'AA',
// 'ABC',
// 'DBA',
// 'aa',
// 'aa',
// 'bb',
// 'c',
// '1',
// '1a',
// '2',
// '2a',
// '2aa',
// '2b',
// '3',
// '3aaa-4c',
// '3aaa-52d',
// '5-6',
// '5dd',
// '7',
// '8',
// '20',
// '33a-35',
// '125a' ]
I don't know what your actual data looks like, but this should nudge you in the right direction, I think:
[ '1a', '2a', '2aa', '5-6', '1', '2', '3', '20' , '7', '8'].sort(function(a,b)
{
if (parseFloat(a) !== parseFloat(b))
{
return parseFloat(a) - parseFloat(b);
}
if (a.charAt(0) !== b.charAt(0))
{//just to be on the safe side
return +(a[0]) - +(b[0]);
}
return a.length - b.length;
});
//result:
["1", "1a", "2", "2a", "2aa", "3", "5-6", "7", "8", "20"]