Jason Evans
Jason Evans

Reputation: 29186

Sort string array by analysing date details in those strings

I have a requirement for the project I'm working on right now which is proving a bit tricky for me.

Basically I have to sort an array of items based on the Text property of those items:

Here are my items:

var answers = [],
    answer1 = { Id: 1, Text: '3-4 weeks ago' },
    answer2 = { Id: 2, Text: '1-2 weeks ago' },
    answer3 = { Id: 3, Text: '7-8 weeks ago' },
    answer4 = { Id: 4, Text: '5-6 weeks ago' },
    answer5 = { Id: 5, Text: '1-2 days ago' },
    answer6 = { Id: 6, Text: 'More than 1 month ago' };

answers.push(answer1);
answers.push(answer2);
answers.push(answer3);
answers.push(answer4);
answers.push(answer5);
answers.push(answer6);

I need to analyse the Text property of each item so that, after the sorting, the array looks like this:

answers[0] = { Id: 6, Text: 'More than 1 month ago' }
answers[1] = { Id: 3, Text: '7-8 weeks ago' }
answers[2] = { Id: 4, Text: '5-6 weeks ago' }
answers[3] = { Id: 1, Text: '3-4 weeks ago' }
answers[4] = { Id: 2, Text: '1-2 weeks ago' }
answers[5] = { Id: 5, Text: '1-2 days ago' }

The logic is that, the furthest away the date, the more high priority it is, so it should appear first in the array. So "1-2 days" is less of a priority then "7-8 weeks".

So the logic is that, I need to extract the number values, and then the units (e.g. days, weeks) and somehow sort the array based on those details.

Quite honestly I'm finding it very difficult to come up with a solution, and I'd appreciate any help.

Edit: Regards the data I'm working with, it's coming back from a web service, and I don't have the ability to change the text. So I have to work with it as is.

Upvotes: 0

Views: 131

Answers (2)

jfriend00
jfriend00

Reputation: 707706

You can use a custom sort function that parses the values out of the text and compares them numerically as described here.

var answers = [
    { Id: 1, Text: '3-4 weeks ago' },
    { Id: 2, Text: '1-2 weeks ago' },
    { Id: 3, Text: '7-8 weeks ago' },
    { Id: 4, Text: '5-6 weeks ago' },
    { Id: 5, Text: '1-2 days ago' },
    { Id: 6, Text: 'More than 1 month ago' }
];

answers.sort(function(a, b) {
    function getVal(item) {
        var val;
        // find first number
        var match = item.Text.match(/\d+/);
        if (match) {
            val = parseInt(match[0], 10);
        } else {
            val = 10000;   // set to very high number
        }
        if (item.Text.indexOf("More than") !== -1) {
            ++val;
        }
        if (item.Text.indexOf("week") !== -1) {
            val *= 7;
        } else if (item.Text.indexOf("month") !== -1) {
            val *= 30;
        }
        return(val);
    }
    return(getVal(b) - getVal(a));
});

Working demo: http://jsfiddle.net/jfriend00/cf3D7/

You can obviously fine tune the getVal() function to support whatever parsing logic you want.

If your array is large, you can make it perform better by precomputing the sort index and storing it in each object so the custom sort function just directly compares two numbers rather than recalculates it every time. But, arrays less than 100 items that's probably irrelevant.

Also, it is unclear how you want to sort "More than 1 month ago" and "7-8 weeks". My current algorithm treats "More than 1 month ago" as 1 month, but you can tweak the parsing logic if you have a particular rule in mind. Once you have the custom sort function, the key to your sort logic is all in the implementation of the getVal() function which you can tweak to support whatever syntax you want to support.

Here's a version that precomputes the sortKey and thus performs a lot better for large arrays:

(function() {
    function getVal(item) {
        var val;
        // find first number
        var match = item.Text.match(/\d+/);
        if (match) {
            val = parseInt(match[0], 10);
        } else {
            val = 10000;   // set to very high number
        }
        if (item.Text.indexOf("More than") !== -1) {
            ++val;
        }
        if (item.Text.indexOf("week") !== -1) {
            val *= 7;
        } else if (item.Text.indexOf("month") !== -1) {
            val *= 30;
        }
        return(val);
    }

    for (var i = 0, len = answers.length; i < len; i++) {
        answers[i].sortKey = getVal(answers[i]);
    }
    answers.sort(function(a, b) {
        return(b.sortKey - a.sortKey);
    });
})()

P.S. Note the much more efficient array declaration syntax I used in the jsFiddle.

Upvotes: 2

user1759942
user1759942

Reputation: 1350

seems to me, a good way, use indexOf() method and search each element in the array for words, like month, day, weeks, etc.. and then after use indexOf to search for numbers. To extract the text you can use substring. so for example for the string '1-2 weeks ago' you'd say str.substring(0, indexOf '-') so this would get everything from the first character to the dash. so you'd extract he first number, even if it is a 3 or 4 digit number. (you might also catch the '-' character, so it might have to be indexOf('-') - 1.. research that.)

A good way to do this, search the array for the largest unit of time.. look for anything with 'years', if none found, search for months, etc.. then have a temporary array and copy all the elements that contain the unit of time you are currently looking for. so for your example, you'd search for years, find none, search for months, find 1 element containing the word month(s), and move that into the temp array. then you'd analyze all the elements in your temp array and find the one with the largest numbers. so in the case of your example you'd only have the one, but say your answer looked like

answer1 = { Id: 1, Text: '3-4 weeks ago' },
answer2 = { Id: 2, Text: '1-2 weeks ago' },
answer3 = { Id: 3, Text: '7-8 weeks ago' },
answer4 = { Id: 4, Text: '6 months ago' },
answer5 = { Id: 5, Text: '5-6 weeks ago' },
answer6 = { Id: 6, Text: '1-2 days ago' },
answer7 = { Id: 7, Text: ' 1 month ago' };
answer8 = { Id: 8, Text: ' 3 months ago' };

then you'd have 3 elements with the month unit, and you'd look at all 3 and order them based on number, so 6 months, 3 months, 1 month. then, either a 3rd and final array for the sorted, or just rearanging the main array. I think a new 'Sorted' array would work better. so you'd put these 3 elements with the month unit into the new sorted array.

Then you'd go to weeks, you'd find the 4 elements with weeks, move them to the temp array, analyze the numbers in them, order them by size, and then stick those in the sorted array, then move on to days etc..

htp a little anyways

Upvotes: 0

Related Questions