¿Cómo ordenar una matriz asociativa por sus valores en Javascript?

84

Tengo la matriz asociativa:

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

¿Cuál es la forma más elegante de ordenar (descender) por sus valores donde el resultado sería una matriz con los índices respectivos en este orden:

sub2, sub3, sub1, sub4, sub0
John Smith
fuente
1
Dado que las propiedades de los objetos no tienen un orden definido por el lenguaje, no puede (excepto, quizás, en algunos motores JS dependiendo de la forma particular en que implementaron las propiedades).
Quentin

Respuestas:

113

Javascript no tiene "matrices asociativas" de la forma en que las piensa. En su lugar, simplemente tiene la capacidad de establecer las propiedades del objeto utilizando una sintaxis similar a una matriz (como en su ejemplo), además de la capacidad de iterar sobre las propiedades de un objeto.

El resultado de esto es que no hay garantía en cuanto al orden en el que itera sobre las propiedades, por lo que no hay nada como una clasificación para ellas. En su lugar, deberá convertir las propiedades de su objeto en una matriz "verdadera" (que garantiza el orden). Aquí hay un fragmento de código para convertir un objeto en una matriz de dos tuplas (matrices de dos elementos), clasificarlo como lo describe y luego iterar sobre él:

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
}

Puede que le resulte más natural envolver esto en una función que reciba una devolución de llamada:

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
fuente
1
la función tuples.sort se puede limpiar a tuples.sort (function (a, b) {return a [1] - b [1];});
stot
2
@stot: si sus valores son todos números (como en el ejemplo del autor de la pregunta), absolutamente. Parece que proporcioné distraídamente una función de comparación que también funciona con cadenas. :-)
Ben Blank
@Ben: sí, tienes razón, la versión proporcionada es más general ;-)
stot
@Ben, muchas gracias por esta publicación. Con respecto a la función de comparación de cadenas, ¿utiliza una comparación de valores ASCII?
jjwdesign
@jjwdesign, asumiendo que la cadena no ha sido codificada, se ordenará por punto de código Unicode.
Ben Blank
85

En lugar de corregirlo en la semántica de una 'matriz asociativa', creo que esto es lo que quiere:

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

para navegadores realmente antiguos , use esto en su lugar:

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

Vuelves un objeto (como el tuyo) y obtienes una matriz de claves - eh propiedades - ordenadas de forma descendente por el valor (numérico) de los valores, eh, del, eh, objeto.

Esto solo funciona si sus valores son numéricos. Modifique lo poco que function(a,b)hay para cambiar el mecanismo de clasificación para que funcione de forma ascendente o trabaje por stringvalores (por ejemplo). Dejado como ejercicio para el lector.

commonpike
fuente
1
Esto funciona perfectamente y es menos código que la respuesta anterior
jshill103
1
Debo señalar que la mayoría de los navegadores de hoy en día solo son compatibles con Object.keys () - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
commonpike
¿Por qué object.keys es mejor?
john ktejik
1
@johnktejik esta respuesta fue de 2012 :-) Object.keys () es un estándar hoy en día, y es más corto y probablemente más rápido.
commonpike
2
... :) Estaba cansado, déjame borrar esto
manu
16

Discusión continua y otras soluciones cubiertas en ¿Cómo ordenar una matriz (asociativa) por valor? con la mejor solución (para mi caso) siendo saml (citado a continuación).

Las matrices solo pueden tener índices numéricos. Debería volver a escribir esto como un objeto o una matriz de objetos.

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 te gusta el status.pushmétodo, puedes ordenarlo con:

status.sort(function(a,b) {
    return a.val - b.val;
});
Papa Juan Pablo II
fuente
¡Excelente! Para hacer y ordenar alfabéticamente por nombre: devuelva a.name> b.name.
mpemburn
1
Para la ordenación alfabética, compare los valores de cadena en el mismo caso porque los sort()trata de manera diferente. Me confundió durante una hora hasta que encontré este stackoverflow.com/questions/6712034/… Ejemplo; a.name.toLowerCase() > b.name.toLowerCase()
Dylan Valade
5

Realmente no existe tal cosa como una "matriz asociativa" en JavaScript. Lo que tienes ahí es solo un objeto antiguo. Funcionan como matrices asociativas, por supuesto, y las claves están disponibles, pero no hay semántica en torno al orden de las claves.

Puede convertir su objeto en una matriz de objetos (pares clave / valor) y ordenar eso:

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

Entonces llamarías a eso con una función comparadora.

Puntiagudo
fuente
+1 me ganaste. Estaba escribiendo una explicación y un código muy similar.
Gonchuki
4

Aquí hay una variación de la respuesta de ben blank, si no le gustan las tuplas.

Esto le ahorra algunos caracteres.

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 Quijote
fuente
4

No se requieren complicaciones innecesarias ...

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;
}
golpear
fuente
El resultado de esto es una matriz de matrices, no un mapa
Daniel
var stuff = {"a":1 , "b":3 , "c":0 } sortMapByValue(stuff) [Array[2], Array[2], Array[2]]
Daniel
Creo que esto es lo más limpio. Simplemente agregue una conversión a un mapa al final
Daniel
1

utilizo $ .each de jquery pero puedes hacerlo con un bucle for, una mejora es esta:

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

Entonces ahora puedes hacer

    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);
Demóstenes
fuente
1

El mejor enfoque para el caso específico aquí, en mi opinión, es el que sugirió commonpike . Una pequeña mejora que sugiero que funciona en los navegadores modernos es:

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

Esto podría aplicarse fácilmente y funcionar muy bien en el caso específico aquí para que pueda hacer:

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

Además de esto, proporciono aquí una función más "genérica" ​​que puede usar para ordenar incluso en una gama más amplia de situaciones y que mezcla la mejora que acabo de sugerir con los enfoques de las respuestas de Ben Blank (ordenando también valores de cadena) y PopeJohnPaulII ( ordenando por campo / propiedad de objeto específico) y le permite decidir si desea un orden ascendente o descendente, aquí está:

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

Puede probar las funcionalidades probando algo como el siguiente código:

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

Como ya dije, puede recorrer las claves ordenadas si necesita hacer cosas

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

Por último, pero no menos importante, algunas referencias útiles a Object.keys y Array.sort

danicotra
fuente
0

Solo para que esté disponible y alguien esté buscando tipos basados ​​en tuplas. Esto comparará el primer elemento del objeto en la matriz, el segundo elemento y así sucesivamente. es decir, en el ejemplo siguiente, se comparará primero por "a", luego por "b" y así sucesivamente.

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

SALIDAS

 [ { 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
fuente
-4

La respuesta de @ commonpike es "la correcta", pero como continúa comentando ...

la mayoría de los navegadores de hoy en día solo admiten Object.keys()

Sí ... Object.keys()es mucho mejor .

Pero, ¿qué es aún mejor ? ¡Duh, ya está 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 Grey
fuente