Thanh Trung
Thanh Trung

Reputation: 3804

How can I partially sort an array based on some condition?

How can I partially sort an array in JavaScript, based on some conditions that I choose?

Given an array

var tab = [
    {html: 'This is a test'},
    {html: 'Locked item', locked: true},
    {html: 'Another item'},
    {html: 'Another one'}
    //and 100 more items...
];

The 2nd items is called "locked", it should stays at the same place (2nd place) after the array is sorted

var tab = [
    {html: 'Another item'},
    {html: 'Locked item', locked: true},
    {html: 'Another one'},
    {html: 'This is a test'},
    //and 100 more items...
];

This is the complete code I currently have:

var tab = [
    {html: 'This is a test'},
    {html: 'Locked item', locked: true},
    {html: 'Another item'},
    {html: 'Another one'}
    //and more items...
];

tab.sort(function(a,b){
    var aVal = a.html.toLowerCase();
    var bVal = b.html.toLowerCase();
    if (aVal===bVal)
        return 0;
    else
        return aVal < bVal ? -1 : 1;

    //how to handle locked items?
});

//desire output
/*var tab = [
    {html: 'Another item'},
    {html: 'Locked item', locked: true},
    {html: 'Another one'},
    {html: 'This is a test'},
    //and more items...
];*/

console.log(tab);

See fiddle

Upvotes: 2

Views: 2011

Answers (4)

MaxArt
MaxArt

Reputation: 22617

The problem here is that sort is used for linear sorting, nothing else. Using it outside its scope is not reliable, also because browsers can perform some optimizations on the way it's executed.

So, you essentially have to linear sort just a part of your array. The best thing you can do is pulling out the locked elements, keep their indexes, perform the sort and putting the locked elements back in in the right positions.

var fixed = [], i;
for (i = tab.length; i--;) {
    if (tab[i].locked) {
        fixed.unshift({index: i, item: tab[i]});
        tab.splice(i, 1);
    }
}

tab.sort(...); // Perform your sort

for (i = 0; i < fixed.length; i++)
    tab.splice(fixed[i].index, 0, fixed[i].item);

Don't be puzzled by the fact that I performed a backward for and used unshift istead of push: it's to keep the correct indexes.

The reversed loop is because when splice is executed it alters the array, so with a forward loop i should be adjusted accordingly in order to not skip items. But that would also mess the index that's recorded in fixed. So we do a backward loop instead and use unshift to keep fixed sorted by the index property.

Keep in mind that splice is kind of slow, so for larger arrays you may want to use some more optimized algorithms. If the array has no more than some dozens fixed elements, it's ok I guess.

Upvotes: 3

roobeedeedada
roobeedeedada

Reputation: 521

Probably could be optimised, but removing all locked items and storing their initial positions, then adding them back into the sorted array should work:

var tab = [
    {html: 'This is a test'},
    {html: 'Locked item', locked: true},
    {html: 'Another item'},
    {html: 'Another one'}
    //and more items...
];

var stored = {}, newTab = [];
for(var i = 0, iLimit = tab.length; i < iLimit; i++) {
    if(tab[i].locked) {
        stored[i] = tab[i];
    } else {
        newTab.push(tab[i]);
    }
}

newTab.sort(function(a,b){
    var aVal = a.html.toLowerCase();
    var bVal = b.html.toLowerCase();
    if (aVal===bVal) {
        return 0;
    } else {
        return aVal < bVal ? -1 : 1;
    }
});

for(var indice in stored) {
    newTab.splice(indice, 0, stored[indice]);
}

console.log(newTab);

http://jsfiddle.net/qW7uH/2/

Upvotes: 1

ptf
ptf

Reputation: 3370

Alternatively, you could filter out all the locked items, an place them in a separate array like this:

[
    { obj: ..., index: ...}, 
    { ... }
] 

This keeps the index for each object. Then you could put them in after you have sorted. Seems like a little hassle though.

Look at Array.prototype.filter for filtering, but beware that it doesn't work in IE8 without a polyfill.

Upvotes: 0

Cerbrus
Cerbrus

Reputation: 72857

This partially works:

tab.sort(function(a,b){
    if(a.locked || b.locked)
        return 0;
    if (a.html < b.html)
        return -1;
    if (a.html > b.html)
        return 1;
    return 0;
});

The only problem is that it can't move the 1st item after the locked item, due to the way the sorting algorithm used by sort works. It does, however, sort the last 2+ items in your example.

Your best bet to get it to work the way you want, is to pull the locked elements from the array, save their index in a variable, sort the array, and then re-insert the elements again.

Upvotes: 2

Related Questions