Reputation: 60574
I have an array of strings that consist of an optional two-letter string signifying spring or fall, followed by a four-digit year, i.e. as one of the following examples:
var example_data = ["HT2014", "VT2013", "2017"];
I'd like to sort this array so that it is primarily sorted on the year (i.e. the four digits, as numbers) and then (if the years are equal) it is sorted so that VT is first, HT is in the middle and entries that do not specify spring or fall are last.
If I've understood the JavaScript sort()
function correctly, I should be able to implement a sortFunction
that tells me which of two objects should be first, and then just make a call to data.sort(sortFunction)
.
I've also started working on such a sortFunction
, and come up with the following:
function specialSort(a,b) {
var as = a.split("T");
var bs = b.split("T");
if (as[1] != bs[1]) {
return as[1] - bs[1];
} else {
// The year is equal.
// How do I sort on term?
}
}
As the comments signify, I have no clue on what to do to get the sorting on "HT"
, "VT"
and ""
correct (except maybe a ridiculous series of nested if
s...). (Also, I know the above code will fail for the third item in the example data, since "2017.split("T")
will only have 1 element. I'll deal with that...)
Is this a good approach? If yes - how do I complete the function to do what I want? If no - what should I do instead?
Upvotes: 1
Views: 716
Reputation: 10603
I took the liberty of using underscore:
var example_data = ["2002","HT2014", "VT2013", "2017", "VT2002", "HT2013"];
var split = _.groupBy(example_data, function(val){ return val.indexOf('T') === -1});
var justYears = split[true].sort();
var yearAndTerm = split[false].sort(function(a,b){
var regex = /([HV])T(\d\d\d\d)/;
var left = regex.exec(a);
var right = regex.exec(b);
return left[2].localeCompare(right[2]) || right[1].localeCompare(left[1]);
});
var sorted = yearAndTerm.concat(justYears);
console.log(sorted);
Here is the fiddle: http://jsfiddle.net/8KHGu/ :)
Upvotes: 0
Reputation: 664484
I'll deal with that...
You can do that by getting the last item from the array, instead of the second:
var lastCmp = as.pop() - bs.pop();
if (lastCmp) // != 0
return lastCmp;
else
// compare on as[0] / bs[0], though they might be undefined now
how do I complete the function to do what I want?
You will need a comparison index table. Similiar to @Jack's switch
statement, it allows you to declare custom orderings:
var orderingTable = {
"V": 1,
"H": 2
// …
},
def = 3;
var aindex = orderingTable[ as[0] ] || def, // by as[0]
bindex = orderingTable[ bs[0] ] || def; // by bs[0]
return aindex - bindex;
If you don't want a table like this, you can use an array as well:
var ordering = ["V", "H" /*…*/];
var *index = ordering.indexOf(*key)+1 || ordering.length+1;
Upvotes: 0
Reputation: 173562
It could be shorter, but this approach calculates a sorting key first, which is then used to sort the array.
Generating the sorting key is very explicit and easy to understand, which always helps me when creating a sort algorithm.
// sorting key = <year> + ('A' | 'B' | 'C')
function getitemkey(item)
{
var parts = item.match(/^(HT|VT)?(\d{4})$/);
switch (parts[1]) {
case 'VT': return parts[2] + 'A'; // VT goes first
case 'HT': return parts[2] + 'B'; // HT is second
}
return parts[2] + 'C'; // no prefix goes last
}
function cmp(a, b)
{
var ka = getitemkey(a),
kb = getitemkey(b);
// simple key comparison
if (ka > kb) {
return 1;
} else if (ka < kb) {
return -1;
}
return 0;
}
["HT2014", "VT2013", "2017", 'HT2013', '2013'].sort(cmp);
Upvotes: 1
Reputation: 74028
I'd use a regular expression with captures and compare on the parts
function compare(a, b) {
var re = /([HV]T)?(\d\d\d\d)/;
var ma = re.exec(a);
var mb = re.exec(b);
// compare the years
if (ma[2] < mb[2])
return -1;
if (ma[2] > mb[2])
return 1;
// years are equal, now compare the prefixes
if (ma[1] == mb[1])
return 0;
if (ma[1] == 'VT')
return -1;
if (mb[1] == 'VT')
return 1;
if (ma[1] == 'HT')
return -1;
return 1;
}
Upvotes: 0