Tomas Aschan
Tomas Aschan

Reputation: 60574

Javascript sort on on part of string

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 ifs...). (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

Answers (4)

Mike Hogan
Mike Hogan

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

Bergi
Bergi

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

Ja͢ck
Ja͢ck

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

Olaf Dietsche
Olaf Dietsche

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

Related Questions