Como classificar uma matriz associativa por seus valores em Javascript?

84

Eu tenho a matriz associativa:

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

Qual é a maneira mais elegante de classificar (decrescente) por seus valores, onde o resultado seria uma matriz com os respectivos índices nesta ordem:

sub2, sub3, sub1, sub4, sub0
John smith
fonte
1
Uma vez que as propriedades do objeto não têm uma ordem definida pela linguagem - você não pode (exceto, talvez, em alguns mecanismos JS, dependendo da maneira particular como eles implementaram as propriedades).
Quentin

Respostas:

115

Javascript não tem "matrizes associativas" da maneira como você as considera. Em vez disso, você simplesmente tem a capacidade de definir as propriedades do objeto usando a sintaxe do tipo array (como no seu exemplo), além da capacidade de iterar nas propriedades de um objeto.

O resultado disso é que não há garantia quanto à ordem em que você itera nas propriedades, portanto, não há nada como uma classificação para elas. Em vez disso, você precisará converter as propriedades do objeto em uma matriz "verdadeira" (que garante a ordem). Aqui está um snippet de código para converter um objeto em uma matriz de duas tuplas (matrizes de dois elementos), classificando-o conforme você descreve e, em seguida, iterando sobre ele:

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
}

Você pode achar mais natural envolver isso em uma função que receba um retorno de chamada:

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
fonte
1
a função tuples.sort pode ser limpa até tuples.sort (function (a, b) {return a [1] - b [1];});
stot
2
@stot - Se seus valores forem todos números (como no exemplo do autor da pergunta), com certeza. Parece que distraidamente forneci uma função de comparação que também funciona com strings. :-)
Ben Blank
@ Ben: Sim, você está certo, a versão fornecida é mais geral ;-)
stot
@Ben, muito obrigado por esta postagem. Com relação à função de comparação de String, ela usa uma comparação de valores ASCII?
jjwdesign
@jjwdesign, supondo que a string não tenha sido codificada, ela será classificada por ponto de código Unicode.
Ben Blank
86

Em vez de corrigi-lo sobre a semântica de uma 'matriz associativa', acho que é isso que você quer:

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

para navegadores muito antigos , use este:

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

Você despeja em um objeto (como o seu) e obtém uma matriz das chaves - eh propriedades - de volta, classificada em ordem decrescente pelo valor (numérico) dos, eh, valores do, eh, objeto.

Isso só funciona se seus valores forem numéricos. Tweek o pouco function(a,b)lá dentro para mudar o mecanismo de classificação para trabalhar de forma ascendente ou trabalhar por stringvalores (por exemplo). Deixado como um exercício para o leitor.

pique comum
fonte
1
Isso funciona perfeitamente e é menos código do que a resposta acima
jshill103
1
Devo observar que a maioria dos navegadores hoje em dia apenas suporta Object.keys () - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
commonpike
por que object.keys é melhor?
john ktejik
1
@johnktejik esta resposta foi de 2012 :-) Object.keys () é um padrão hoje em dia, e é mais curto e provavelmente mais rápido.
commonpike de
2
... :) Eu estava cansado, deixe-me deletar isto
manu
16

Discussão contínua e outras soluções abordadas em Como classificar uma matriz (associativa) por valor? com a melhor solução (para o meu caso) sendo por saml (citado abaixo).

Matrizes podem ter apenas índices numéricos. Você precisaria reescrever isso como um objeto ou uma 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});

Se gostar do status.pushmétodo, você pode classificá-lo com:

status.sort(function(a,b) {
    return a.val - b.val;
});
PopeJohnPaulII
fonte
Excelente! Para fazer e classificar o alfa em nome: return a.name> b.name.
mpemburn de
1
Para a classificação alfabética, compare os valores da string no mesmo caso porque os sort()trata de maneira diferente. Isso me confundiu por uma hora até que encontrei este stackoverflow.com/questions/6712034/… Exemplo; a.name.toLowerCase() > b.name.toLowerCase()
Dylan Valade,
5

Realmente não existe algo como uma "matriz associativa" em JavaScript. O que você tem aí é apenas um objeto simples e antigo. Eles funcionam como arrays associativos, é claro, e as chaves estão disponíveis, mas não há semântica em torno da ordem das chaves.

Você pode transformar seu objeto em uma matriz de objetos (pares de chave / valor) e classificá-lo:

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

Então você chamaria isso com uma função de comparador.

Pontudo
fonte
+1 você me venceu. Eu estava escrevendo uma explicação e um código muito semelhantes.
gonchuki
4

Aqui está uma variação da resposta de Ben blank, se você não gosta de tuplas.

Isso economiza alguns 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 Quixote
fonte
4

Nenhuma complicação desnecessária necessária ...

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
fonte
A saída disso é uma matriz de matrizes, não um mapa
Daniel
var stuff = {"a":1 , "b":3 , "c":0 } sortMapByValue(stuff) [Array[2], Array[2], Array[2]]
Daniel
Eu acho que isso é mais limpo. Basta adicionar uma conversão a um mapa no final
Daniel
1

eu uso $ .each do jquery, mas você pode fazer isso com um loop for, uma melhoria é 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;
              }               
     };

Então agora você pode fazer

    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);
demosthenes
fonte
1

A melhor abordagem para o caso específico aqui, em minha opinião, é aquela sugerida pelo commonpike . Uma pequena melhoria que eu sugiro que funciona em navegadores modernos é:

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

Isso pode se aplicar facilmente e funcionar muito bem no caso específico aqui, para que você possa fazer:

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

Além disso, apresento aqui uma função mais "genérica" ​​que você pode usar para classificar até mesmo em uma gama mais ampla de situações e que mistura a melhoria que acabei de sugerir com as abordagens das respostas de Ben Blank (classificando também valores de string) e PopeJohnPaulII ( classificação por campo / propriedade de objeto específico) e permite que você decida se deseja uma ordem ascendente ou descendente, aqui 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});
    }
  }
}

Você pode testar as funcionalidades tentando algo como o seguinte 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 eu já disse, você pode fazer um loop sobre as chaves classificadas se precisar fazer outras coisas

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, mas não menos importante, algumas referências úteis a Object.keys e Array.sort

danicotra
fonte
0

Só para que ele esteja lá fora e alguém esteja procurando por classificações baseadas em tupla. Isso irá comparar o primeiro elemento do objeto na matriz, do que o segundo elemento e assim por diante. ou seja, no exemplo abaixo, ele irá comparar primeiro por "a", depois por "b" e assim por diante.

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

OUPUTS

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

A resposta de @commonpike é "a certa", mas à medida que ele comenta ...

a maioria dos navegadores hoje em dia apenas suporta Object.keys()

Sim .. Object.keys()é muito melhor .

Mas o que é ainda melhor ? Duh, é isso aí 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
fonte