Iterar sobre uma matriz associativa Javascript em ordem classificada

109

Digamos que eu tenha um array associativo Javascript (também conhecido como hash, também conhecido como dicionário):

var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;

Como posso iterar as chaves em ordem classificada? Se isso ajuda a simplificar as coisas, eu nem preciso dos valores (são todos apenas o número 1).

Mike
fonte
11
por que você está usando a nova construção Array () e depois como um objeto?
Luke Schafer
@Luke .. Eu fiz isso no começo também, vindo de um background em PHP. Eu aprendi agora :)
alex
20
@Luke: porque sou inexperiente, ao que parece. Você pode postar da maneira correta em uma resposta?
mike
4
Você pode simplesmente criar qualquer objeto. Em Javascript não há diferença entre um dicionário / "array nomeado" e um objeto regular. Portanto, você pode acessar a ['b'] com ab e vice-versa. A maneira mais curta de criar um objeto é a = {};.
Lodewijk

Respostas:

124

Você não pode iterar diretamente sobre eles, mas pode encontrar todas as chaves e então apenas classificá-las.

var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;    

function keys(obj)
{
    var keys = [];

    for(var key in obj)
    {
        if(obj.hasOwnProperty(key))
        {
            keys.push(key);
        }
    }

    return keys;
}

keys(a).sort(); // ["a", "b", "z"]

No entanto, não há necessidade de transformar a variável 'a' em um array. Você o está usando apenas como um objeto e deve criá-lo assim:

var a = {};
a["key"] = "value";
Mateus
fonte
28
Você deve sempre verificar no forloop se obj.hasOwnProperty(key).
viam0Zah
3
@Lalit - se você está se referindo ao comentário do Torok, é porque você não tem nada interferindo no protótipo do objeto, no qual você não pode confiar.
Luke Schafer
1
+1 para Torok. Seria bom se a resposta incluísse hasOwnProperty ().
Jon Onstott
136

Você pode usar o método interno Object.keys :

var sorted_keys = Object.keys(a).sort()

(Observação: isso não funciona em navegadores muito antigos que não oferecem suporte a EcmaScript5, principalmente IE6, 7 e 8. Para estatísticas atualizadas detalhadas, consulte esta tabela )

Molnarg
fonte
@ michael667 Provavelmente porque o IE 7 e 8 ainda são amplamente usados ​​(infelizmente, obrigado MS)
Alexander Reifinger
1
O IE7 está em 0,5% e o IE8 está em 8% a partir de agora, felizmente.
molnarg
3
if (!Object.keys) { Object.keys = function (obj) { var op, result = []; for (op in obj) { if (obj.hasOwnProperty(op) { result.push(op) } } return result }
Jordan Reiter
Eu amo este, obrigado. aqui está meu código aproveitando isso, $ (Object.keys (list)). map (function (i, e) {return n + '=' + list [n];}). get (). join ('&'); // concat for url querystring
Elaine
1
Atualização de 2016: esta provavelmente deve ser a resposta aceita
rinogo
14

você pode até mesmo prototipá-lo no objeto:

Object.prototype.iterateSorted = function(worker)
{
    var keys = [];
    for (var key in this)
    {
        if (this.hasOwnProperty(key))
            keys.push(key);
    }
    keys.sort();

    for (var i = 0; i < keys.length; i++)
    {
        worker(this[ keys[i] ]);
    }
}

e o uso:

var myObj = { a:1, b:2 };
myObj.iterateSorted(function(value)
{
    alert(value);
} 
Luke Schafer
fonte
3
Eu votei a favor desta resposta que parecia muito boa, mas acabou quebrando jquery :( stackoverflow.com/questions/1827458/… e em geral é considerada uma ideia muito ruim "Você nunca deve estender Object.prototype. Faz muito mais do que quebrar o jQuery; ele quebra completamente o recurso "objeto-como-hashtables" do Javascript. Não faça isso. Você pode perguntar a John Resig e ele lhe dirá a mesma coisa. "
msanjay
Você sabe o que? Eu também odeio protótipos :) Eu nunca os uso e desencorajo ativamente seu uso. Eu meio que me senti assim há 3,5 anos quando escrevi esta resposta, mas sugeri mesmo assim ... obrigado por fornecer a informação. Como um aparte, ele NÃO DEVE quebrar frameworks, pois eles SEMPRE devem usar hasOwnProperty ao iterar objetos
Luke Schafer
Aqui está um exemplo usando os valores em vez das chaves para classificação , mantendo o key -> valuerelacionamento.
Xeoncross
6

Concordo com a resposta de Swingley , e acho que é um ponto importante que muitas dessas soluções mais elaboradas estão faltando. Se você está preocupado apenas com as chaves na matriz associativa e todos os valores são '1', simplesmente armazene as 'chaves' como valores em uma matriz.

Ao invés de:

var a = { b:1, z:1, a:1 };
// relatively elaborate code to retrieve the keys and sort them

Usar:

var a = [ 'b', 'z', 'a' ];
alert(a.sort());

A única desvantagem disso é que você não pode determinar se uma chave específica é definida com tanta facilidade. Veja esta resposta para a função javascript emArray para uma resposta a esse problema. Um problema com a solução apresentada é que a.hasValue('key')será um pouco mais lenta do que a['key']. Isso pode ou não importar em seu código.

Grant Wagner
fonte
3

Não há uma maneira concisa de manipular diretamente as "chaves" de um objeto Javascript. Não é realmente projetado para isso. Você tem a liberdade de colocar seus dados em algo melhor do que um objeto normal (ou um Array, como seu código de exemplo sugere)?

Em caso afirmativo, e se sua pergunta pudesse ser reformulada como "Que objeto semelhante a um dicionário devo usar se quiser iterar sobre as chaves em ordem classificada?" então você pode desenvolver um objeto como este:

var a = {
  keys : new Array(),
  hash : new Object(),
  set : function(key, value) {
    if (typeof(this.hash[key]) == "undefined") { this.keys.push(key); }
    this.hash[key] = value;
  },
  get : function(key) {
    return this.hash[key];
  },
  getSortedKeys : function() {
    this.keys.sort();
    return this.keys;
  }
};

// sample use
a.set('b',1);
a.set('z',1);
a.set('a',1);
var sortedKeys = a.getSortedKeys();
for (var i in sortedKeys) { print(sortedKeys[i]); }

Se você não tiver controle sobre o fato de que os dados estão em um objeto regular, este utilitário converterá o objeto regular em seu dicionário totalmente funcional:

a.importObject = function(object) {
  for (var i in object) { this.set(i, object); }
};

Esta foi uma definição de objeto (em vez de uma função construtora reutilizável) para simplificar; edite à vontade.

Travis Wilson
fonte
2

Pegue as chaves no primeiro forloop, classifique-as e use o resultado classificado no segundo forloop.

var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;

var b = [];
for (k in a) b.push(k);
b.sort();
for (var i = 0; i < b.length; ++i) alert(b[i]);
pts
fonte
2

Você pode usar a keysfunção da biblioteca underscore.js para obter as chaves e o sort()método de matriz para classificá-las:

var sortedKeys = _.keys(dict).sort();

A keysfunção no código-fonte do sublinhado:

// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
    if (obj !== Object(obj)) throw new TypeError('Invalid object');
    var keys = [];
    for (var key in obj) if (_.has(obj, key)) keys.push(key);
    return keys;
};    

// Shortcut function for checking if an object has a given property directly
// on itself (in other words, not on a prototype).
_.has = function(obj, key) {
    return hasOwnProperty.call(obj, key);
};
Eugene Yarmash
fonte
0
<script type="text/javascript">
    var a = {
        b:1,
        z:1,
        a:1
    }; // your JS Object
    var keys = [];
    for (key in a) {
        keys.push(key);
    }
    keys.sort();
    var i = 0;
    var keyslen = keys.length;
    var str = '';
    //SORTED KEY ITERATION
    while (i < keyslen) {
        str += keys[i] + '=>' + a[keys[i]] + '\n';
        ++i;
    }
    alert(str);
    /*RESULT:
    a=>1
    b=>1
    z=>1
    */
</script>
Fran Corpier
fonte
0

var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;


var keys=Object.keys(a).sort();
for(var i=0,key=keys[0];i<keys.length;key=keys[++i]){
  document.write(key+' : '+a[key]+'<br>');
}

Vlad V
fonte
0

Eu realmente gosto da ideia de protótipo de @luke-schafer, mas também ouço o que ele está dizendo sobre os problemas com protótipos. Que tal usar uma função simples?

function sortKeysAndDo( obj, worker ) {
  var keys = Object.keys(obj);
  keys.sort();
  for (var i = 0; i < keys.length; i++) {
     worker(keys[i], obj[keys[i]]);
  }
}

function show( key, value ) {
  document.write( key + ' : ' + value +'<br>' );
}

var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;

sortKeysAndDo( a, show);

var my_object = { 'c': 3, 'a': 1, 'b': 2 };

sortKeysAndDo( my_object, show);

Isso parece eliminar os problemas com protótipos e ainda fornecer um iterador classificado para objetos. No entanto, não sou realmente um guru do JavaScript, então adoraria saber se essa solução tem falhas ocultas que perdi.

EFC
fonte