117

I have an array of objects to sort. Each object has two parameters: Strength and Name

objects = []
object[0] = {strength: 3, name: "Leo"}
object[1] = {strength: 3, name: "Mike"}

I want to sort first by Strength and then by name alphabetically. I am using the following code to sort by the first parameter. How do I sort then by the second?

function sortF(ob1,ob2) {
  if (ob1.strength > ob2.strength) {return 1}
  else if (ob1.strength < ob2.strength){return -1}
  return 0;
};

Thanks for your help.

(I am using Array.sort() with the aforementioned sortF as the sort comparison function passed into it.)

10 Answers 10

142

Expand your sort function to be like this;

function sortF(ob1,ob2) {
    if (ob1.strength > ob2.strength) {
        return 1;
    } else if (ob1.strength < ob2.strength) { 
        return -1;
    }

    // Else go to the 2nd item
    if (ob1.name < ob2.name) { 
        return -1;
    } else if (ob1.name > ob2.name) {
        return 1
    } else { // nothing to split them
        return 0;
    }
}

A < and > comparison on strings is an alphabetic comparison.

Sign up to request clarification or add additional context in comments.

Comments

100

This little function is often handy when sorting by multiple keys:

cmp = function(a, b) {
    if (a > b) return +1;
    if (a < b) return -1;
    return 0;
}

or, more concisely,

cmp = (a, b) => (a > b) - (a < b)

Which works because in javascript:

true - true // gives 0
false - false // gives 0
true - false // gives 1
false - true // gives -1

Apply it like this:

array.sort(function(a, b) { 
    return cmp(a.strength,b.strength) || cmp(a.name,b.name)
})

Javascript is really missing Ruby's spaceship operator, which makes such comparisons extremely elegant.

8 Comments

cough Perl's spaceship operator.
Thank you. I've been search for this kernel of knowledge for a while now... I just didn't know how to word it. Especially the spaceship operator.
Love this, implemented into an app. Would this work with 3 keys also? I'm not there yet, but might need a 3rd sorting.
@AndySmith: sure, cmp(a, b) || cmp(c, d) || cmp(e, f) etc etc
@AndyB: it has been fixed since then ;)
|
52

You could chain the sort order with logical OR.

objects.sort(function (a, b) {
    return a.strength - b.strength || a.name.localeCompare(b.name);
});

4 Comments

That's brilliant! But what about the case where strength can be undefined, and we want all objects with defined strength to go first. Is there any solution that's as elegant as this?
maybe something like this: return ((a.strength || Number.MAX_VALUE) - (b.strength || Number.MAX_VALUE)) || a.name.localeCompare(b.name);
yes, you could add a default value for nonexistent parts like return ((a.strength || Infinity) - (b.strength || Infinity)) || a.name.localeCompare(b.name);. but that relies on the possible value of strength.
works like a charm return a.z - b.z || a.x - b.x || a.y - b.y
18

When I was looking for an answer to this very question, the answers I found on StackOverflow weren't really what I hoped for. So I created a simple, reusable function that does exactly this. It allows you to use the standard Array.sort, but with firstBy().thenBy().thenBy() style. https://github.com/Teun/thenBy.js

PS. This is the second time I post this. The first time was removed by a moderator saying "Please don't make promotional posts for your own work". I'm not sure what the rules are here, but I was trying to answer this question. I'm very sorry that it is my own work. Feel free to remove again, but please point me to the rule involved then.

4 Comments

You should put a firstname, lastname example in the Readme.md as everyone is after that ^^, I will give this lib a try, looks good.
Yeah, good point, I guess. It is tempting to show really arbitrary combinations of fields, but the readme should indeed start with the most common looked after scenario. Thanks for the tip.
This is great. I cannot understand why the moderators would remove something so useful. If, it required purchasing, then that might be different, although, I guess it’s up to us to decide. We are all grown ups here and can make intelligent decisions.
I can't speak for the mods, but in addition to self-promotion my guess why they removed it: link-only answers don't contain an answer. Copying the 40 lines of library source code into your answer probably isn't ideal either, but copying a few lines of code or describing the algorithm implemented in the library would make the contents of your answer stand by itself.
5

steve's answer, but prettier.

objects.sort(function(a,b)
{
  if(a.strength > b.strength) {return  1;}
  if(a.strength < b.strength) {return -1;}
  if(a.name     > b.name    ) {return  1;}
  if(a.name     < b.name    ) {return -1;}
  return 0;
}

Comments

1
function sortF(ob1,ob2) {
  if (ob1.strength > ob2.strength) {return 1}
  else if (ob1.strength < ob2.strength) {return -1}
  else if (ob1.name > ob2.name) {return 1}
  return -1;
};

EDIT: Sort by strength, then if strength is equal, sort by name. The case where strength and name are equal in both objects doesn't need to be accounted for seperately, since the final return of -1 indicates a less-than-or-equal-to relationship. The outcome of the sort will be correct. It might make it run faster or slower, I don't know. If you want to be explicit, just replace

return -1;

with

else if (ob1.name < ob2.name) {return -1}
return 0;

1 Comment

You missed out the equal case.
1

Find 'sortFn' function below. This function sorts by unlimited number of parameters(such as in c#: SortBy(...).ThenBy(...).ThenByDesc(...)).

function sortFn() {
    var sortByProps = Array.prototype.slice.call(arguments),
        cmpFn = function(left, right, sortOrder) {
            var sortMultiplier = sortOrder === "asc" ? 1 : -1;

            if (left > right) {
                return +1 * sortMultiplier;
            }
            if (left < right) {
                return -1 * sortMultiplier;
            }
            return 0;
        };


    return function(sortLeft, sortRight) {
        // get value from object by complex key
        var getValueByStr = function(obj, path) {
            var i, len;

            //prepare keys
            path = path.replace('[', '.');
            path = path.replace(']', '');
            path = path.split('.');

            len = path.length;

            for (i = 0; i < len; i++) {
                if (!obj || typeof obj !== 'object') {
                    return obj;
                }
                obj = obj[path[i]];
            }

            return obj;
        };

        return sortByProps.map(function(property) {
            return cmpFn(getValueByStr(sortLeft, property.prop), getValueByStr(sortRight, property.prop), property.sortOrder);
        }).reduceRight(function(left, right) {
            return right || left;
        });
    };
}

var arr = [{
    name: 'marry',
    LocalizedData: {
        'en-US': {
            Value: 10000
        }
    }
}, {
    name: 'larry',
    LocalizedData: {
        'en-US': {
            Value: 2
        }
    }
}, {
    name: 'marry',
    LocalizedData: {
        'en-US': {
            Value: 100
        }
    }
}, {
    name: 'larry',
    LocalizedData: {
        'en-US': {
            Value: 1
        }
    }
}];
document.getElementsByTagName('pre')[0].innerText = JSON.stringify(arr)

arr.sort(sortFn({
    prop: "name",
    sortOrder: "asc"
}, {
    prop: "LocalizedData[en-US].Value",
    sortOrder: "desc"
}));

document.getElementsByTagName('pre')[1].innerText = JSON.stringify(arr)
pre {
    font-family: "Courier New" Courier monospace;
    white-space: pre-wrap;
}
Before:
<pre></pre>
Result:
<pre></pre>

1 Comment

can you make an arrow function version of this one?
0

With ES6 you can do

array.sort(function(a, b) { 
 return SortFn(a.strength,b.strength) || SortFn(a.name,b.name)
})

 private sortFn(a, b): number {
    return a === b ? 0 : a < b ? -1 : 1;
}

Comments

0

Here is the function I use. It will do an arbitrary number.

function Sorter(){
  
  var self = this;
  this.sortDefs = [];
  
  for (let i = 0; i < arguments.length; i++) {
  // Runs 5 times, with values of step 0 through 4.
    this.sortDefs.push(arguments[i]);
  }
  
  this.sort = function(a, b){
  
    for (let i = 0; i < self.sortDefs.length; i++) {

        if (a[self.sortDefs[i]] < b[self.sortDefs[i]]) { 
          return -1;
        } else if (a[self.sortDefs[i]] > b[self.sortDefs[i]]) {
          return 1
        }
    }
    
    return 0;
  }
}

data.sort(new Sorter('category','name').sort);

Comments

-1

In 2018 you can use just sort() ES6 function, that do exactly, what you want. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.