Come ordinare un array associativo in base ai suoi valori in Javascript?


84

Ho l'array associativo:

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

Qual è il modo più elegante per ordinare (decrescente) in base ai suoi valori in cui il risultato sarebbe un array con i rispettivi indici in questo ordine:

sub2, sub3, sub1, sub4, sub0

1
Poiché le proprietà degli oggetti non hanno un ordine definito dal linguaggio, non è possibile (tranne, forse, in alcuni motori JS a seconda del modo particolare in cui hanno implementato le proprietà).
Quentin

Risposte:


113

Javascript non ha "array associativi" nel modo in cui li pensi. Invece, hai semplicemente la possibilità di impostare le proprietà degli oggetti usando la sintassi di tipo array (come nel tuo esempio), oltre alla possibilità di iterare sulle proprietà di un oggetto.

Il risultato di ciò è che non vi è alcuna garanzia sull'ordine in cui si itera sulle proprietà, quindi non c'è niente come un ordinamento per loro. Invece, dovrai convertire le proprietà dell'oggetto in un array "vero" (che garantisce l'ordine). Ecco uno snippet di codice per convertire un oggetto in un array di due tuple (array di due elementi), ordinandolo come descrivi, quindi iterandolo su di esso:

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
}

Potresti trovare più naturale racchiudere questo in una funzione che accetta un 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>


1
la funzione tuples.sort può essere ripulita in tuples.sort (function (a, b) {return a [1] - b [1];});
stot

2
@stot - Se i tuoi valori sono tutti numeri (come nell'esempio del richiedente), assolutamente. Mi sembra di aver fornito distrattamente una funzione di confronto che funziona anche con le stringhe. :-)
Ben Blank

@ Ben: sì hai ragione, la versione fornita è più generale ;-)
stot

@ Ben, grazie mille per questo intervento. Per quanto riguarda la funzione di confronto delle stringhe, utilizza un confronto di valori ASCII?
jjwdesign

@jjwdesign, supponendo che la stringa non sia stata codificata, verrà ordinata per punto di codice Unicode.
Ben Blank,

85

Invece di correggerti sulla semantica di un 'array associativo', penso che questo sia quello che vuoi:

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

per browser molto vecchi , usa invece questo:

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

Esegui il dump di un oggetto (come il tuo) e ottieni un array di chiavi - eh, proprietà - indietro, ordinato decrescente per il valore (numerico) dei valori, eh, dell'oggetto, eh.

Funziona solo se i tuoi valori sono numerici. Tweek il piccolo function(a,b)lì per cambiare il meccanismo di ordinamento per lavorare in modo crescente o lavorare per i stringvalori (ad esempio). Lasciato come esercizio per il lettore.


1
Funziona perfettamente ed è meno codice della risposta sopra
jshill103

1
Dovrei notare che la maggior parte dei browser oggigiorno supporta solo Object.keys () - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
commonpike

perché object.keys è migliore?
john ktejik

1
@johnktejik questa risposta era del 2012 :-) Object.keys () è uno standard al giorno d'oggi, ed è più breve e probabilmente più veloce.
commonpike

2
... :) Ero stanco, lasciatemi cancellare questo
manu

16

Discussione continua e altre soluzioni trattate in Come ordinare un array (associativo) in base al valore? con la migliore soluzione (per il mio caso) essendo saml (citato sotto).

Gli array possono avere solo indici numerici. Dovresti riscriverlo come Object o come 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});

Se ti piace il status.pushmetodo, puoi ordinarlo con:

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

Eccellente! Da fare e ordina alfabeticamente in base al nome: restituisci a.name> b.name.
mpemburn

1
Per l'ordinamento alfabetico, confronta i valori di stringa nello stesso caso perché sort()li tratta in modo diverso. Mi ha incasinato per un'ora finché non ho trovato questo stackoverflow.com/questions/6712034/… Esempio; a.name.toLowerCase() > b.name.toLowerCase()
Dylan Valade

5

Non c'è davvero niente come un "array associativo" in JavaScript. Quello che hai lì è solo un semplice oggetto vecchio. Funzionano un po 'come gli array associativi, ovviamente, e le chiavi sono disponibili ma non c'è semantica sull'ordine delle chiavi.

Puoi trasformare il tuo oggetto in un array di oggetti (coppie chiave / valore) e ordinarlo:

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

Quindi lo chiameresti con una funzione di confronto.


+1 mi hai battuto. Stavo scrivendo una spiegazione e un pezzo di codice molto simili.
gonchuki

4

Ecco una variazione della risposta di Ben Blank, se non ti piacciono le tuple.

Questo ti fa risparmiare alcuni caratteri.

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

4

Nessuna complicazione inutile richiesta ...

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

L'output di questo è un array di array, non una mappa
Daniel

var stuff = {"a":1 , "b":3 , "c":0 } sortMapByValue(stuff) [Array[2], Array[2], Array[2]]
Daniel

Penso che questo sia il più pulito. Basta aggiungere una conversione a una mappa alla fine
Daniel

1

io uso $ .each di jquery ma puoi farlo con un ciclo for, un miglioramento è questo:

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

Quindi ora puoi farlo

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

1

L'approccio migliore per il caso specifico qui, a mio parere, è quello suggerito da un picco comune . Un piccolo miglioramento che suggerirei che funziona nei browser moderni è:

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

Questo potrebbe essere facilmente applicabile e funzionare alla grande nel caso specifico qui, quindi puoi fare:

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

Oltre a questo, fornisco qui una funzione più "generica" ​​che puoi usare per ordinare anche in una gamma più ampia di situazioni e che mescola il miglioramento che ho appena suggerito con gli approcci delle risposte di Ben Blank (ordinando anche i valori di stringa) e PopeJohnPaulII ( ordinamento per campo / proprietà oggetto specifico) e consente di decidere se si desidera un ordine ascendente o discendente, eccolo:

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

Puoi testare le funzionalità provando qualcosa di simile al seguente codice:

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

Come ho già detto, puoi scorrere le chiavi ordinate se hai bisogno di fare cose

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

Infine, ma non meno importante, alcuni riferimenti utili a Object.keys e Array.sort


0

Solo così è là fuori e qualcuno sta cercando ordinamenti basati su tuple. Questo confronterà il primo elemento dell'oggetto nell'array, rispetto al secondo elemento e così via. ad esempio, nell'esempio seguente, verrà confrontato prima con "a", quindi con "b" e così via.

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

USCITE

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

-4

La risposta di @ commonpike è "quella giusta", ma mentre continua a commentare ...

la maggior parte dei browser oggigiorno supporta solo Object.keys()

Sì .. Object.keys()è MOLTO meglio .

Ma cosa c'è di meglio ? Duh, è dentro 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' ]

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.