Comment trier un tableau associatif par ses valeurs en Javascript?

84

J'ai le tableau associatif:

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

Quelle est la manière la plus élégante de trier (décroissant) par ses valeurs où le résultat serait un tableau avec les indices respectifs dans cet ordre:

sub2, sub3, sub1, sub4, sub0
John Smith
la source
1
Puisque les propriétés d'objet n'ont pas d'ordre défini dans le langage - vous ne pouvez pas (sauf, peut-être, dans certains moteurs JS en fonction de la manière particulière dont ils implémentent les propriétés).
Quentin

Réponses:

113

Javascript n'a pas de "tableaux associatifs" comme vous les pensez. Au lieu de cela, vous avez simplement la possibilité de définir les propriétés d'un objet à l'aide d'une syntaxe de type tableau (comme dans votre exemple), ainsi que la possibilité d'itérer sur les propriétés d'un objet.

Le résultat est qu'il n'y a aucune garantie quant à l' ordre dans lequel vous itérez sur les propriétés, il n'y a donc rien de tel qu'un tri pour eux. Au lieu de cela, vous devrez convertir les propriétés de votre objet en un "vrai" tableau (qui garantit l'ordre). Voici un extrait de code pour convertir un objet en un tableau de deux tuples (tableaux à deux éléments), en le triant comme vous le décrivez, puis en l'itérant:

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
}

Vous trouverez peut-être plus naturel d'encapsuler ceci dans une fonction qui prend un rappel:

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>

Ben Blank
la source
1
la fonction tuples.sort peut être nettoyée jusqu'à tuples.sort (function (a, b) {return a [1] - b [1];});
stot
2
@stot - Si vos valeurs sont toutes des nombres (comme dans l'exemple du demandeur), absolument. Il semble que j'ai fourni distraitement une fonction de comparaison qui fonctionne également avec des chaînes. :-)
Ben Blank
@Ben: oui vous avez raison, la version fournie est plus générale ;-)
stot
@Ben, merci beaucoup pour cette publication. Concernant la fonction de comparaison de chaînes, utilise-t-elle une comparaison de valeurs ASCII?
jjwdesign
@jjwdesign, en supposant que la chaîne n'a pas été encodée, elle sera triée par point de code Unicode.
Ben Blank
85

Au lieu de vous corriger sur la sémantique d'un 'tableau associatif', je pense que c'est ce que vous voulez:

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

pour les navigateurs vraiment anciens , utilisez plutôt ceci:

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

Vous videz un objet (comme le vôtre) et obtenez un tableau des clés - eh propriétés - en arrière, triées par ordre décroissant de la valeur (numérique) des, eh, valeurs de l'objet, eh.

Cela ne fonctionne que si vos valeurs sont numériques. Tweek le peu function(a,b)là-dedans pour changer le mécanisme de tri pour travailler par ordre croissant, ou travailler pour des stringvaleurs (par exemple). Laissé comme exercice pour le lecteur.

grand brochet
la source
1
Cela fonctionne parfaitement et est moins de code que la réponse ci-dessus
jshill103
1
Je dois noter que la plupart des navigateurs de nos jours ne supportent que Object.keys () - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
commonpike
pourquoi object.keys est-il meilleur?
john ktejik
1
@johnktejik cette réponse date de 2012 :-) Object.keys () est un standard de nos jours, et c'est plus court et probablement plus rapide.
commonpike
2
... :) J'étais fatigué, laissez-moi supprimer ceci
manu
16

Discussion continue et autres solutions abordées dans Comment trier un tableau (associatif) par valeur? avec la meilleure solution (pour mon cas) étant par saml (cité ci-dessous).

Les tableaux ne peuvent avoir que des index numériques. Vous auriez besoin de réécrire ceci en tant qu'objet ou tableau d'objets.

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});

Si vous aimez la status.pushméthode, vous pouvez la trier avec:

status.sort(function(a,b) {
    return a.val - b.val;
});
PapeJohnPaulII
la source
Excellent! A faire et tri alpha sur le nom: retournez a.name> b.name.
mpemburn
1
Pour le tri alphabétique, comparez les valeurs de chaîne dans le même cas car les sort()traite différemment. Cela m'a dérangé pendant une heure jusqu'à ce que je trouve ce stackoverflow.com/questions/6712034/... Exemple; a.name.toLowerCase() > b.name.toLowerCase()
Dylan Valade
5

Il n'y a vraiment pas de "tableau associatif" en JavaScript. Ce que vous avez là n'est qu'un vieil objet. Ils fonctionnent un peu comme des tableaux associatifs, bien sûr, et les clés sont disponibles mais il n'y a pas de sémantique autour de l'ordre des clés.

Vous pouvez transformer votre objet en un tableau d'objets (paires clé / valeur) et trier cela:

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;
}

Ensuite, vous appelleriez cela avec une fonction de comparaison.

Pointu
la source
+1 tu m'as battu. J'écrivais une explication et un morceau de code très similaires.
gonchuki
4

Voici une variante de la réponse de Ben Blank, si vous n'aimez pas les tuples.

Cela vous fait gagner quelques caractères.

var keys = [];
for (var key in sortme) {
  keys.push(key);
}

keys.sort(function(k0, k1) {
  var a = sortme[k0];
  var b = sortme[k1];
  return a < b ? -1 : (a > b ? 1 : 0);
});

for (var i = 0; i < keys.length; ++i) {
  var key = keys[i];
  var value = sortme[key];
  // Do something with key and value.
}
don Quichotte
la source
4

Aucune complication inutile requise ...

function sortMapByValue(map)
{
    var tupleArray = [];
    for (var key in map) tupleArray.push([key, map[key]]);
    tupleArray.sort(function (a, b) { return a[1] - b[1] });
    return tupleArray;
}
bop
la source
La sortie de ceci est un tableau de tableaux, pas une carte
Daniel
var stuff = {"a":1 , "b":3 , "c":0 } sortMapByValue(stuff) [Array[2], Array[2], Array[2]]
Daniel
Je pense que c'est le plus propre. Ajoutez simplement une conversion à une carte à la fin
Daniel
1

J'utilise $ .each de jquery mais vous pouvez le faire avec une boucle for, une amélioration est la suivante:

        //.ArraySort(array)
        /* Sort an array
         */
        ArraySort = function(array, sortFunc){
              var tmp = [];
              var aSorted=[];
              var oSorted={};

              for (var k in array) {
                if (array.hasOwnProperty(k)) 
                    tmp.push({key: k, value:  array[k]});
              }

              tmp.sort(function(o1, o2) {
                    return sortFunc(o1.value, o2.value);
              });                     

              if(Object.prototype.toString.call(array) === '[object Array]'){
                  $.each(tmp, function(index, value){
                      aSorted.push(value.value);
                  });
                  return aSorted;                     
              }

              if(Object.prototype.toString.call(array) === '[object Object]'){
                  $.each(tmp, function(index, value){
                      oSorted[value.key]=value.value;
                  });                     
                  return oSorted;
              }               
     };

Alors maintenant tu peux faire

    console.log("ArraySort");
    var arr1 = [4,3,6,1,2,8,5,9,9];
    var arr2 = {'a':4, 'b':3, 'c':6, 'd':1, 'e':2, 'f':8, 'g':5, 'h':9};
    var arr3 = {a: 'green', b: 'brown', c: 'blue', d: 'red'};
    var result1 = ArraySort(arr1, function(a,b){return a-b});
    var result2 = ArraySort(arr2, function(a,b){return a-b});
    var result3 = ArraySort(arr3, function(a,b){return a>b});
    console.log(result1);
    console.log(result2);       
    console.log(result3);
démosthène
la source
1

La meilleure approche pour le cas spécifique ici, à mon avis, est celui commonpike suggéré. Une petite amélioration que je suggère qui fonctionne dans les navigateurs modernes est:

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

Cela pourrait s'appliquer facilement et fonctionner très bien dans le cas spécifique ici afin que vous puissiez faire:

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]]
}

En plus de cela, je propose ici une fonction plus "générique" que vous pouvez utiliser pour trier même dans un plus large éventail de situations et qui mélange l'amélioration que je viens de suggérer avec les approches des réponses de Ben Blank (triant également les valeurs de chaîne) et PopeJohnPaulII ( tri par champ / propriété d'objet spécifique) et vous permet de décider si vous voulez un ordre ascendant ou descendant, le voici:

// 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});
    }
  }
}

Vous pouvez tester les fonctionnalités en essayant quelque chose comme le code suivant:

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
*/

Comme je l'ai déjà dit, vous pouvez boucler sur des clés triées si vous avez besoin de faire des choses

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]]
}

Dernier point, mais non le moindre, quelques références utiles à Object.keys et Array.sort

danicotra
la source
0

Juste pour que ce soit là-bas et que quelqu'un recherche des tris basés sur des tuple. Cela comparera le premier élément de l'objet dans le tableau, au deuxième élément et ainsi de suite. c'est à dire dans l'exemple ci-dessous, il comparera d'abord par "a", puis par "b" et ainsi de suite.

let arr = [
    {a:1, b:2, c:3},
    {a:3, b:5, c:1},
    {a:2, b:3, c:9},
    {a:2, b:5, c:9},
    {a:2, b:3, c:10}    
]

function getSortedScore(obj) {
    var keys = []; 
    for(var key in obj[0]) keys.push(key);
    return obj.sort(function(a,b){
        for (var i in keys) {
            let k = keys[i];
            if (a[k]-b[k] > 0) return -1;
            else if (a[k]-b[k] < 0) return 1;
            else continue;
        };
    });
}

console.log(getSortedScore(arr))

SORTIES

 [ { a: 3, b: 5, c: 1 },
  { a: 2, b: 5, c: 9 },
  { a: 2, b: 3, c: 10 },
  { a: 2, b: 3, c: 9 },
  { a: 1, b: 2, c: 3 } ]
Shayan C
la source
-4

La réponse de @ commonpike est "la bonne", mais comme il continue à commenter ...

la plupart des navigateurs ne prennent aujourd'hui en charge que Object.keys()

Ouais ... Object.keys()c'est bien mieux .

Mais quoi de mieux ? Duh, c'est dedans coffeescript!

sortedKeys = (x) -> Object.keys(x).sort (a,b) -> x[a] - x[b]

sortedKeys
  'a' :  1
  'b' :  3
  'c' :  4
  'd' : -1

[ 'd', 'a', 'b', 'c' ]

Alex Gray
la source