How to sort an associative array by its values in Javascript?

118,820

Solution 1

Javascript doesn't have "associative arrays" the way you're thinking of them. Instead, you simply have the ability to set object properties using array-like syntax (as in your example), plus the ability to iterate over an object's properties.

The upshot of this is that there is no guarantee as to the order in which you iterate over the properties, so there is nothing like a sort for them. Instead, you'll need to convert your object properties into a "true" array (which does guarantee order). Here's a code snippet for converting an object into an array of two-tuples (two-element arrays), sorting it as you describe, then iterating over it:

var tuples = [];

for (var key in obj) tuples.push([key, obj[key]]);

tuples.sort(function(a, b) {
    a = a[1];
    b = b[1];

    return a < b ? -1 : (a > b ? 1 : 0);
});

for (var i = 0; i < tuples.length; i++) {
    var key = tuples[i][0];
    var value = tuples[i][1];

    // do something with key and value
}

You may find it more natural to wrap this in a function which takes a callback:

function bySortedValue(obj, callback, context) {
  var tuples = [];

  for (var key in obj) tuples.push([key, obj[key]]);

  tuples.sort(function(a, b) {
    return a[1] < b[1] ? 1 : a[1] > b[1] ? -1 : 0
  });

  var length = tuples.length;
  while (length--) callback.call(context, tuples[length][0], tuples[length][1]);
}

bySortedValue({
  foo: 1,
  bar: 7,
  baz: 3
}, function(key, value) {
  document.getElementById('res').innerHTML += `${key}: ${value}<br>`
});
<p id='res'>Result:<br/><br/><p>

Solution 2

Instead of correcting you on the semantics of an 'associative array', I think this is what you want:

function getSortedKeys(obj) {
    var keys = Object.keys(obj);
    return keys.sort(function(a,b){return obj[b]-obj[a]});
}

for really old browsers, use this instead:

function getSortedKeys(obj) {
    var keys = []; for(var key in obj) keys.push(key);
    return keys.sort(function(a,b){return obj[b]-obj[a]});
}

You dump in an object (like yours) and get an array of the keys - eh properties - back, sorted descending by the (numerical) value of the, eh, values of the, eh, object.

This only works if your values are numerical. Tweek the little function(a,b) in there to change the sorting mechanism to work ascending, or work for string values (for example). Left as an exercise for the reader.

Solution 3

Continued discussion & other solutions covered at How to sort an (associative) array by value? with the best solution (for my case) being by saml (quoted below).

Arrays can only have numeric indexes. You'd need to rewrite this as either an Object, or an Array of Objects.

var status = new Array();
status.push({name: 'BOB', val: 10});
status.push({name: 'TOM', val: 3});
status.push({name: 'ROB', val: 22});
status.push({name: 'JON', val: 7});

If you like the status.push method, you can sort it with:

status.sort(function(a,b) {
    return a.val - b.val;
});

Solution 4

There really isn't any such thing as an "associative array" in JavaScript. What you've got there is just a plain old object. They work kind-of like associative arrays, of course, and the keys are available but there's no semantics around the order of keys.

You could turn your object into an array of objects (key/value pairs) and sort that:

function sortObj(object, sortFunc) {
  var rv = [];
  for (var k in object) {
    if (object.hasOwnProperty(k)) rv.push({key: k, value:  object[k]});
  }
  rv.sort(function(o1, o2) {
    return sortFunc(o1.key, o2.key);
  });
  return rv;
}

Then you'd call that with a comparator function.

Solution 5

The best approach for the specific case here, in my opinion, is the one commonpike suggested. A little improvement I'd suggest that works in modern browsers is:

// aao is the "associative array" you need to "sort"
Object.keys(aao).sort(function(a,b){return aao[b]-aao[a]});

This could apply easily and work great in the specific case here so you can do:

let aoo={};
aao["sub2"]=1;
aao["sub0"]=-1;
aao["sub1"]=0;
aao["sub3"]=1;
aao["sub4"]=0;

let sk=Object.keys(aao).sort(function(a,b){return aao[b]-aao[a]});

// now you can loop using the sorted keys in `sk` to do stuffs
for (let i=sk.length-1;i>=0;--i){
 // do something with sk[i] or aoo[sk[i]]
}

Besides of this, I provide here a more "generic" function you can use to sort even in wider range of situations and that mixes the improvement I just suggested with the approaches of the answers by Ben Blank (sorting also string values) and PopeJohnPaulII (sorting by specific object field/property) and lets you decide if you want an ascendant or descendant order, here it is:

// aao := is the "associative array" you need to "sort"
// comp := is the "field" you want to compare or "" if you have no "fields" and simply need to compare values
// intVal := must be false if you need comparing non-integer values
// desc := set to true will sort keys in descendant order (default sort order is ascendant)
function sortedKeys(aao,comp="",intVal=false,desc=false){
  let keys=Object.keys(aao);
  if (comp!="") {
    if (intVal) {
      if (desc) return keys.sort(function(a,b){return aao[b][comp]-aao[a][comp]});
      else return keys.sort(function(a,b){return aao[a][comp]-aao[a][comp]});
    } else {
      if (desc) return keys.sort(function(a,b){return aao[b][comp]<aao[a][comp]?1:aao[b][comp]>aao[a][comp]?-1:0});
      else return keys.sort(function(a,b){return aao[a][comp]<aao[b][comp]?1:aao[a][comp]>aao[b][comp]?-1:0});
    }
  } else {
    if (intVal) {
      if (desc) return keys.sort(function(a,b){return aao[b]-aao[a]});
      else return keys.sort(function(a,b){return aao[a]-aao[b]});
    } else {
      if (desc) return keys.sort(function(a,b){return aao[b]<aao[a]?1:aao[b]>aao[a]?-1:0});
      else return keys.sort(function(a,b){return aao[a]<aao[b]?1:aao[a]>aao[b]?-1:0});
    }
  }
}

You can test the functionalities trying something like the following code:

let items={};
items['Edward']=21;
items['Sharpe']=37;
items['And']=45;
items['The']=-12;
items['Magnetic']=13;
items['Zeros']=37;
//equivalent to:
//let items={"Edward": 21, "Sharpe": 37, "And": 45, "The": -12, ...};

console.log("1: "+sortedKeys(items));
console.log("2: "+sortedKeys(items,"",false,true));
console.log("3: "+sortedKeys(items,"",true,false));
console.log("4: "+sortedKeys(items,"",true,true));
/* OUTPUT
1: And,Sharpe,Zeros,Edward,Magnetic,The
2: The,Magnetic,Edward,Sharpe,Zeros,And
3: The,Magnetic,Edward,Sharpe,Zeros,And
4: And,Sharpe,Zeros,Edward,Magnetic,The
*/

items={};
items['k1']={name:'Edward',value:21};
items['k2']={name:'Sharpe',value:37};
items['k3']={name:'And',value:45};
items['k4']={name:'The',value:-12};
items['k5']={name:'Magnetic',value:13};
items['k6']={name:'Zeros',value:37};

console.log("1: "+sortedKeys(items,"name"));
console.log("2: "+sortedKeys(items,"name",false,true));
/* OUTPUT
1: k6,k4,k2,k5,k1,k3
2: k3,k1,k5,k2,k4,k6
*/

As I already said, you can loop over sorted keys if you need doing stuffs

let sk=sortedKeys(aoo);
// now you can loop using the sorted keys in `sk` to do stuffs
for (let i=sk.length-1;i>=0;--i){
 // do something with sk[i] or aoo[sk[i]]
}

Last, but not least, some useful references to Object.keys and Array.sort

Share:
118,820

Related videos on Youtube

John Smith
Author by

John Smith

Updated on July 20, 2021

Comments

  • John Smith
    John Smith almost 3 years

    I have the associative array:

    array["sub2"] = 1;
    array["sub0"] = -1;
    array["sub1"] = 0;
    array["sub3"] = 1;
    array["sub4"] = 0;
    

    What is the most elegant way to sort (descending) by its values where the result would be an array with the respective indices in this order:

    sub2, sub3, sub1, sub4, sub0
    
    • Quentin
      Quentin about 13 years
      Since object properties have no language defined order - you can't (except, perhaps, in some JS engines by depending on the particular way that they implemented properties).
  • gonchuki
    gonchuki about 13 years
    +1 you beat me to it. I was writing a very similar explanation and piece of code.
  • stot
    stot over 11 years
    the tuples.sort function can be cleaned up to tuples.sort(function(a, b) { return a[1] - b[1]; });
  • Ben Blank
    Ben Blank over 11 years
    @stot — If your values are all numbers (as in the asker's example), absolutely. I appear to have absent-mindedly provided a comparison function which works with strings as well. :-)
  • stot
    stot over 11 years
    @Ben: yes you are right, the version provided is more general ;-)
  • jjwdesign
    jjwdesign almost 11 years
    @Ben, thank you very much for this posting. Concerning the String comparison function, does it use an ASCII value comparison?
  • Ben Blank
    Ben Blank almost 11 years
    @jjwdesign, assuming the string hasn't been encoded, it will sort by Unicode code point.
  • mpemburn
    mpemburn about 10 years
    Excellent! To do and alpha sort on name: return a.name > b.name.
  • Daniel
    Daniel almost 9 years
    The output of this is an array of arrays, not a map
  • Daniel
    Daniel almost 9 years
    var stuff = {"a":1 , "b":3 , "c":0 } sortMapByValue(stuff) [Array[2], Array[2], Array[2]]
  • Daniel
    Daniel almost 9 years
    I think this is cleanest. Just add a conversion to a map at the end
  • commonpike
    commonpike over 8 years
    I should note that most browsers nowadays just support Object.keys() - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • Dylan Valade
    Dylan Valade over 7 years
    For the alphabetic sort, compare the string values in the same case because sort() treats them differently. It messed me up for an hour until I found this stackoverflow.com/questions/6712034/… Example; a.name.toLowerCase() > b.name.toLowerCase()
  • john ktejik
    john ktejik almost 6 years
    why is object.keys any better?
  • commonpike
    commonpike almost 6 years
    @johnktejik this answer was from 2012 :-) Object.keys() is a standard nowadays, and its shorter and probably quicker.
  • manu
    manu over 5 years
    ... :) I was tired, let me delete this