Classificar matriz de objetos pelo valor da propriedade string


2763

Eu tenho uma matriz de objetos JavaScript:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

Como posso classificá-los pelo valor de last_nom JavaScript?

Eu sei sort(a,b), mas isso parece funcionar apenas em strings e números. Preciso adicionar um toString()método aos meus objetos?


7
Este script permite que você faça exatamente isso, a menos que você quer escrever sua própria função de comparação ou classificador: thomasfrank.se/sorting_things.html
Baversjo

a maneira mais rápida é usar o módulo de matriz de classificação isomórfica, que funciona nativamente no navegador e no nó, suportando qualquer tipo de entrada, campos computados e ordens de classificação personalizadas.
Lloyd

Respostas:


3923

É fácil escrever sua própria função de comparação:

function compare( a, b ) {
  if ( a.last_nom < b.last_nom ){
    return -1;
  }
  if ( a.last_nom > b.last_nom ){
    return 1;
  }
  return 0;
}

objs.sort( compare );

Ou em linha (com Marco Demaio):

objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0)); 

441
Ou em linha: objs.sort (function (a, b) {return (a.last_nom> b.last_nom)? 1: ((b.last_nom> a.last_nom)? -1: 0);});
Marco Demaio


176
return a.last_nom.localeCompare(b.last_nom)vai funcionar também.
Cerbrus

146
para aqueles que procuram uma espécie onde o campo é numérico, a comparar corpo da função: return a.value - b.value;(ASC)
Andre Figueiredo

20
O @Cerbrus localeCompareé importante ao usar caracteres acentuados em idiomas estrangeiros e também mais elegante.
Marcos Lima

839

Você também pode criar uma função de classificação dinâmica que classifica objetos pelo valor que você passa:

function dynamicSort(property) {
    var sortOrder = 1;
    if(property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a,b) {
        /* next line works with strings and numbers, 
         * and you may want to customize it to your needs
         */
        var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

Então você pode ter uma matriz de objetos como este:

var People = [
    {Name: "Name", Surname: "Surname"},
    {Name:"AAA", Surname:"ZZZ"},
    {Name: "Name", Surname: "AAA"}
];

... e funcionará quando você fizer:

People.sort(dynamicSort("Name"));
People.sort(dynamicSort("Surname"));
People.sort(dynamicSort("-Surname"));

Na verdade, isso já responde à pergunta. A parte abaixo está escrita porque muitas pessoas entraram em contato comigo, reclamando que não funciona com vários parâmetros .

Parâmetros múltiplos

Você pode usar a função abaixo para gerar funções de classificação com vários parâmetros de classificação.

function dynamicSortMultiple() {
    /*
     * save the arguments object as it will be overwritten
     * note that arguments object is an array-like object
     * consisting of the names of the properties to sort by
     */
    var props = arguments;
    return function (obj1, obj2) {
        var i = 0, result = 0, numberOfProperties = props.length;
        /* try getting a different result from 0 (equal)
         * as long as we have extra properties to compare
         */
        while(result === 0 && i < numberOfProperties) {
            result = dynamicSort(props[i])(obj1, obj2);
            i++;
        }
        return result;
    }
}

O que permitiria que você fizesse algo assim:

People.sort(dynamicSortMultiple("Name", "-Surname"));

Matriz de subclassificação

Para os sortudos entre nós que podem usar o ES6, o que permite estender os objetos nativos:

class MyArray extends Array {
    sortBy(...args) {
        return this.sort(dynamicSortMultiple.apply(null, args));
    }
}

Isso permitiria isso:

MyArray.from(People).sortBy("Name", "-Surname");

Observe que os nomes de propriedades no JavaScript podem ser qualquer sequência e se você tiver propriedades começando com um "-" (extremamente improvável e provavelmente não é uma boa ideia), será necessário modificar a função dynamicSort para usar outra coisa como uma classificação reversa indicador.
Ege Özcan

Percebi que o dynamicSort()exemplo acima colocará letras maiúsculas à frente de letras minúsculas. Por exemplo, se eu tenho os valores APd, Aklin, e Abe- os resultados em uma ASC tipo deve ser Abe, Aklin, APd. Mas, com o seu exemplo, os resultados são APd, Abe, Aklin. Enfim, para corrigir esse comportamento?
Lloyd Banks

6
@LloydBanks, se você estiver usando isso estritamente para strings, poderá usar em var result = a[property].localeCompare(b[property]);vez de var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;.
Ege Özcan

1
@ EgeÖzcan Ele quer deve colocar itens para cima ou para baixo, a implementação de aqui eu acabei usando pastebin.com/g4dt23jU
DZH

1
Esta é uma ótima solução, mas você tem um problema se comparar números. Por favor, adicione isto antes da verificação:if( !isNaN(a[property]) ) a[property] = Number(a[property]); if( !isNaN(b[property]) ) b[property] = Number(b[property]);
Ivijan Stefan Stipić

416

No ES6 / ES2015 ou posterior, você pode fazer o seguinte:

objs.sort((a, b) => a.last_nom.localeCompare(b.last_nom));

Antes do ES6 / ES2015

objs.sort(function(a, b) {
    return a.last_nom.localeCompare(b.last_nom)
});

29
isso está disponível desde o JS 1.1, a peça de flecha gorda é a peça ES6 / 2015. Mas ainda é muito útil, e a melhor resposta na minha opinião
Jon Harding

13
@PratikKelwalkar: se você precisar inverter, basta alternar a comparação aeb: objs.sort ((a, b) => b.last_nom.localeCompare (a.last_nom));
Vlad Bezden

é possível usar um índice também para endereçar o campo para classificação: em vez de last_nomusar apenas o número na matriz 1:?
e-bri 29/05

4
@VladBezden obrigado pela sua resposta! Essa solução é a primeira com um esforço programático muito pequeno, resultados de classificação corretos e uma matriz de cadeias de caracteres como: ["Nome1", "Nome10", "Nome2", "outra coisa", "Nome11"]. Eu tenho uma classificação para funcionar corretamente comobjs.sort((a, b) => a.last_nom.localeCompare(b.last_nom, undefined, {numberic: true}));
scipper

1
Para fazer isso com números decrescentes: .sort ((a, b) => b.numberProperty - a.numberProperty). Ascendente: .sort ((a, b) => a.numberProperty - b.numberProperty)
Sebastian Patten

188

underscore.js

use sublinhado, é pequeno e incrível ...

sortBy_.sortBy (list, iterator, [context]) Retorna uma cópia ordenada da lista, classificada em ordem crescente pelos resultados da execução de cada valor no iterador. Iterador também pode ser o nome da string da propriedade a ser classificada (por exemplo, comprimento).

var objs = [ 
  { first_nom: 'Lazslo',last_nom: 'Jamf' },
  { first_nom: 'Pig', last_nom: 'Bodine'  },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

var sortedObjs = _.sortBy( objs, 'first_nom' );

18
David, você poderia editar a resposta para dizer var sortedObjs = _.sortBy( objs, 'first_nom' );. objsvai não ser ordenados-se como um resultado disso. A função retornará uma matriz classificada. Isso tornaria mais explícito.
Jess

10
Para inverter a classificação:var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse();
Erdal G.

2
você precisa carregar o libary javascript "sublinhado":<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"> </script>
e-bri

5
Também disponível Lodashpara aqueles que preferem esse
#

2
No lodash, isso seria o mesmo: var sortedObjs = _.sortBy( objs, 'first_nom' );ou se você quiser em uma ordem diferente:var sortedObjs = _.orderBy( objs, ['first_nom'],['dsc'] );
Travis Heeter 15/11/19

186

Não entenda por que as pessoas tornam isso tão complicado:

objs.sort(function(a, b){
  return a.last_nom > b.last_nom;
});

Para motores mais rigorosos:

objs.sort(function(a, b){
  return a.last_nom == b.last_nom ? 0 : +(a.last_nom > b.last_nom) || -1;
});

Troque o operador para ordená-lo por ordem alfabética inversa.


17
Na verdade, isso não está correto, pois a função usada na classificação deve retornar -1, 0 ou 1, mas a função acima retorna um valor booleano. A classificação funciona bem no chrome, mas falha no PhantomJS, por exemplo. Consulte code.google.com/p/phantomjs/issues/detail?id=1090
schup

Alguns motores são responsáveis ​​pela estupidez, dessa maneira foi meio que explorada. Atualizei minha resposta com uma versão adequada.
P3lim #

19
Sugiro editar para tirar a primeira versão. É mais sucinto, por isso parece mais atraente, mas não funciona, pelo menos não de forma confiável. Se alguém tentar em um navegador e funcionar, talvez nem perceba que está com um problema (principalmente se não tiver lido os comentários). A segunda versão funciona corretamente, portanto não há necessidade da primeira.
Kate

2
@ Simon Eu realmente gostei de ter a versão "levemente errada" primeiro, já que a implementação mais rigorosa leva alguns segundos para analisar e entender e seria muito mais difícil sem ela.
Aaron Sherman

1
@ Lion789 apenas fazer isso:if(a.count == b.count) return a.name > b.name; else return a.count > b.count;
p3lim

62

Se você tiver sobrenomes duplicados, poderá classificá-los por nome.

obj.sort(function(a,b){
  if(a.last_nom< b.last_nom) return -1;
  if(a.last_nom >b.last_nom) return 1;
  if(a.first_nom< b.first_nom) return -1;
  if(a.first_nom >b.first_nom) return 1;
  return 0;
});

@BadFeelingAboutThis é o que significa retornar -1 ou 1? Entendo que -1 significa literalmente que A é menor que B apenas pela sintaxe, mas por que usar 1 ou -1? Vejo que todos estão usando esses números como valores de retorno, mas por quê? Obrigado.
precisa saber é o seguinte

1
@ Chris22 um número negativo retornado significa que bdeve vir depois ana matriz. Se um número positivo for retornado, isso significa que adeve vir depois b. Se 0for retornado, significa que eles são considerados iguais. Você sempre pode ler a documentação: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
BadFeelingAboutThis

@BadFeelingAboutThis agradece pela explicação e pelo link. Acredite ou não, pesquisei vários trechos de código no Google 1, 0, -1antes de perguntar aqui. Só não estava encontrando as informações necessárias.
precisa saber é o seguinte

48

Solução simples e rápida para esse problema usando herança de protótipo:

Array.prototype.sortBy = function(p) {
  return this.slice(0).sort(function(a,b) {
    return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
  });
}

Exemplo / Uso

objs = [{age:44,name:'vinay'},{age:24,name:'deepak'},{age:74,name:'suresh'}];

objs.sortBy('age');
// Returns
// [{"age":24,"name":"deepak"},{"age":44,"name":"vinay"},{"age":74,"name":"suresh"}]

objs.sortBy('name');
// Returns
// [{"age":24,"name":"deepak"},{"age":74,"name":"suresh"},{"age":44,"name":"vinay"}]

Atualização: não modifica mais a matriz original.


5
Não basta retornar outra matriz. mas na verdade classifica o original!
Vinay Aggarwal

Se você quiser ter certeza de que está usando uma classificação natural com números (ou seja, 0,1,2,10,11 etc ...), use parseInt com o conjunto Radix. developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/… so: return (parseInt (a [p], 10)> parseInt (b [p], 10))? 1: (parseInt (a [p], 10) <parseInt (b [p], 10))? -1: 0;
Paul

@codehuntr Obrigado por corrigi-lo. mas acho que, em vez de fazer a função de classificação para fazer essa sensibilização, é melhor fazermos uma função separada para corrigir tipos de dados. Porque a função de classificação não pode dizer qual propriedade conterá que tipo de dados. :)
Vinay Aggarwal

Muito agradável. Inverta as setas para obter o efeito asc / desc.
Abdul Sadik Yalcin

4
nunca, nunca sugerir uma solução que modifica o protótipo de um objeto núcleo
pbanka

39

A partir de 2018, existe uma solução muito mais curta e elegante. Apenas use. Array.prototype.sort () .

Exemplo:

var items = [
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'And', value: 45 },
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 13 },
  { name: 'Zeros', value: 37 }
];

// sort by value
items.sort(function (a, b) {
  return a.value - b.value;
});

7
Na pergunta, as seqüências de caracteres foram usadas para comparação em oposição a números. Sua resposta funciona muito bem para classificar por números, mas não é tão boa para comparações por sequência de caracteres.
smcstewart

O a.value - b.valueusado para comparar os atributos do objeto ( neste caso, números ) pode ser adotado para os vários tempos de dados. Por exemplo, regex pode ser usado para comparar cada par de cadeias vizinhas .
0leg

1
@ 0leg Eu gostaria de ver um exemplo aqui do uso de regex para comparar seqüências de caracteres dessa maneira.
Bob Stein

Essa implementação é muito boa se você precisar classificá-la por ID. Sim, você sugeriu usar o regex para comparar a string vizinha, o que torna a solução mais complicada, enquanto o objetivo desta versão simplificada não seria o caso se o regex fosse usado junto com a solução fornecida. Simplicidade é a melhor.
surendrapanday

33

Resposta antiga que não está correta:

arr.sort((a, b) => a.name > b.name)

ATUALIZAR

Do comentário de Beauchamp:

arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))

Formato mais legível:

arr.sort((a, b) => {
  if (a.name < b.name) return -1
  return a.name > b.name ? 1 : 0
})

Sem ternários aninhados:

arr.sort((a, b) => a.name < b.name ? - 1 : Number(a.name > b.name))

Explicação: Number()será convertido truepara 1e falsepara 0.


Ele funciona, mas o resultado é instável por algum motivo
TitanFighter

@ AO17 não, não vai. Você não pode subtrair strings.
Patrick Roberts

15
Isso deve ser feito:arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))
Jean-François Beauchamp

3
@ Jean-FrançoisBeauchamp, sua solução funciona perfeitamente bem e muito melhor.
Ahsan

o terceiro com número é direto e agradável!
Kojo

29

Em vez de usar uma função de comparação personalizada, você também pode criar um tipo de objeto com o toString()método personalizado (que é invocado pela função de comparação padrão):

function Person(firstName, lastName) {
    this.firtName = firstName;
    this.lastName = lastName;
}

Person.prototype.toString = function() {
    return this.lastName + ', ' + this.firstName;
}

var persons = [ new Person('Lazslo', 'Jamf'), ...]
persons.sort();

29

Lodash.js (superconjunto de Underscore.js )

É bom não adicionar uma estrutura para cada peça simples de lógica, mas confiar em estruturas de utilidade bem testadas pode acelerar o desenvolvimento e reduzir a quantidade de bugs.

Lodash produz código muito limpo e promove uma programação mais funcional estilo de . Em um vislumbre, fica claro qual é a intenção do código.

A questão do OP pode simplesmente ser resolvida como:

const sortedObjs = _.sortBy(objs, 'last_nom');

Mais informações? Por exemplo, temos o seguinte objeto aninhado:

const users = [
  { 'user': {'name':'fred', 'age': 48}},
  { 'user': {'name':'barney', 'age': 36 }},
  { 'user': {'name':'wilma'}},
  { 'user': {'name':'betty', 'age': 32}}
];

Agora podemos usar a abreviação _.propertyuser.age para especificar o caminho para a propriedade que deve ser correspondida. Classificaremos os objetos do usuário pela propriedade age aninhada. Sim, permite a correspondência de propriedades aninhadas!

const sortedObjs = _.sortBy(users, ['user.age']);

Deseja que seja revertido? Sem problemas. Use _.reverse .

const sortedObjs = _.reverse(_.sortBy(users, ['user.age']));

Deseja combinar ambos usando corrente ?

const { chain } = require('lodash');
const sortedObjs = chain(users).sortBy('user.age').reverse().value();

Ou quando você prefere o fluxo sobre a corrente

const { flow, reverse, sortBy } = require('lodash/fp');
const sortedObjs = flow([sortBy('user.age'), reverse])(users); 

28

Você pode usar

Caminho mais fácil: Lodash

( https://lodash.com/docs/4.17.10#orderBy )

Esse método é como _.sortBy, exceto que permite especificar as ordens de classificação dos iterados para classificar. Se pedidos não for especificado, todos os valores serão classificados em ordem crescente. Caso contrário, especifique uma ordem de "desc" para decrescente ou "asc" para ordem de classificação crescente dos valores correspondentes.

Argumentos

coleção (matriz | objeto): a coleção para iterar. [iteratees = [_. identidade]] (Matriz [] | Função [] | Objeto [] | string []): Os iterativos para classificar. [orders] (string []): as ordens de classificação dos iterados.

Devoluções

(Matriz): retorna a nova matriz classificada.


var _ = require('lodash');
var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];

_.orderBy(homes, ['city', 'state', 'zip'], ['asc', 'desc', 'asc']);

23

Há muitas boas respostas aqui, mas gostaria de salientar que elas podem ser estendidas de maneira muito simples para obter uma classificação muito mais complexa. A única coisa que você precisa fazer é usar o operador OR para encadear funções de comparação como esta:

objs.sort((a,b)=> fn1(a,b) || fn2(a,b) || fn3(a,b) )

onde fn1,fn2 ... são as funções de classificação que retorno [-1,0,1]. Isso resulta em "classificação por fn1", "classificação por fn2", que é praticamente igual a ORDER BY no SQL.

Essa solução é baseada no comportamento do ||operador que avalia a primeira expressão avaliada que pode ser convertida em true .

A forma mais simples possui apenas uma função embutida como esta:

// ORDER BY last_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) )

Tendo duas etapas em last_nom, a first_nomordem de classificação ficaria assim:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) || 
                  a.first_nom.localeCompare(b.first_nom)  )

Uma função de comparação genérica pode ser algo como isto:

// ORDER BY <n>
let cmp = (a,b,n)=>a[n].localeCompare(b[n])

Esta função pode ser estendida para suportar campos numéricos, distinção entre maiúsculas e minúsculas, tipos de dados arbitrários etc.

Você pode usá-lo com encadeamento por prioridade de classificação:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> cmp(a,b, "last_nom") || cmp(a,b, "first_nom") )
// ORDER_BY last_nom, first_nom DESC
objs.sort((a,b)=> cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )
// ORDER_BY last_nom DESC, first_nom DESC
objs.sort((a,b)=> -cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )

O ponto aqui é que o JavaScript puro com abordagem funcional pode levar um longo caminho sem bibliotecas externas ou código complexo. Também é muito eficaz, já que nenhuma análise de string precisa ser feita


23

Exemplo de uso:

objs.sort(sortBy('last_nom'));

Roteiro:

/**
 * @description
 * Returns a function which will sort an
 * array of objects by the given key.
 *
 * @param  {String}  key
 * @param  {Boolean} reverse
 * @return {Function}
 */
const sortBy = (key, reverse) => {

  // Move smaller items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  const moveSmaller = reverse ? 1 : -1;

  // Move larger items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  const moveLarger = reverse ? -1 : 1;

  /**
   * @param  {*} a
   * @param  {*} b
   * @return {Number}
   */
  return (a, b) => {
    if (a[key] < b[key]) {
      return moveSmaller;
    }
    if (a[key] > b[key]) {
      return moveLarger;
    }
    return 0;
  };
};

obrigado por analisar isso, estou tentando entender por que os dígitos 1, 0, -1são usados ​​para a classificação. Mesmo com a sua explicação acima, que parece muito boa - ainda não estou entendendo direito. Eu sempre penso -1como ao usar a propriedade length da matriz, ou seja: arr.length = -1significa que o item não foi encontrado. Provavelmente estou misturando as coisas aqui, mas você poderia me ajudar a entender por que os dígitos 1, 0, -1são usados ​​para determinar a ordem? Obrigado.
precisa saber é o seguinte

1
Isso não é totalmente preciso, mas pode ajudar a pensar assim: a função passada para array.sort é chamada uma vez para cada item da matriz, como o argumento chamado "a". O valor de retorno de cada chamada de função é como o índice (número da posição atual) do item "a" deve ser alterado em comparação com o próximo item "b". O índice determina a ordem da matriz (0, 1, 2, etc.) Portanto, se "a" estiver no índice 5 e você retornar -1, então 5 + -1 == 4 (mova-o para mais perto da frente) 5 + 0 == 5 (mantenha-o onde está) etc. Ele percorre a matriz comparando 2 vizinhos por vez até chegar ao final, deixando uma matriz classificada.
21318 Jamie Mason

obrigado por dedicar um tempo para explicar isso ainda mais. Portanto, usando sua explicação e o MDN Array.prototype.sort , vou lhe dizer o que estou pensando: em comparação com ae b, se afor maior que badicionar 1 ao índice de ae colocá-lo para trás b, se afor menor que b, subtraia 1 de ae coloque-o na frente de b. Se ae bsão iguais, adicione 0 a ae deixe-o onde está.
precisa saber é o seguinte

22

Eu não vi essa abordagem específica sugerida, então aqui está um método de comparação conciso que eu gosto de usar que funciona para ambos stringe number:

const objs = [ 
  { first_nom: 'Lazslo', last_nom: 'Jamf'     },
  { first_nom: 'Pig',    last_nom: 'Bodine'   },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

const sortBy = fn => (a, b) => {
  const fa = fn(a);
  const fb = fn(b);
  return -(fa < fb) || +(fa > fb);
};

const getLastName = o => o.last_nom;
const sortByLastName = sortBy(getLastName);

objs.sort(sortByLastName);
console.log(objs.map(getLastName));

Aqui está uma explicação de sortBy():

sortBy()aceita um fnque seleciona qual valor de um objeto usar como comparação e retorna uma função que pode ser passada diretamente para Array.prototype.sort(). Neste exemplo, estamos usando o.last_nomcomo valor para comparação, portanto, sempre que recebermos dois objetos Array.prototype.sort(), como

{ first_nom: 'Lazslo', last_nom: 'Jamf' }

e

{ first_nom: 'Pig', last_nom: 'Bodine' }

nós usamos

(a, b) => {
  const fa = fn(a);
  const fb = fn(b);
  return -(fa < fb) || +(fa > fb);
};

para compará-los.

Lembrando que fn = o => o.last_nom, podemos expandir a função de comparação para o equivalente

(a, b) => {
  const fa = a.last_nom;
  const fb = b.last_nom;
  return -(fa < fb) || +(fa > fb);
};

O ||operador OR lógico tem uma funcionalidade de curto-circuito que é muito útil aqui. Por causa de como funciona, o corpo da função acima significa

if (fa < fb) return -1;
if (fa > fb) return 1;
return 0;

Como um bônus adicional, aqui está o equivalente no ECMAScript 5 sem funções de seta, que infelizmente é mais detalhado:

var objs = [ 
  { first_nom: 'Lazslo', last_nom: 'Jamf'     },
  { first_nom: 'Pig',    last_nom: 'Bodine'   },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

var sortBy = function (fn) {
  return function (a, b) {
    var fa = fn(a);
    var fb = fn(b);
    return -(fa < fb) || +(fa > fb);
  };
};

var getLastName = function (o) { return o.last_nom };
var sortByLastName = sortBy(getLastName);

objs.sort(sortByLastName);
console.log(objs.map(getLastName));


17

Sei que essa pergunta é muito antiga, mas não vi nenhuma implementação semelhante à minha.
Esta versão é baseada no idioma de transformação Schwartzian .

function sortByAttribute(array, ...attrs) {
  // generate an array of predicate-objects contains
  // property getter, and descending indicator
  let predicates = attrs.map(pred => {
    let descending = pred.charAt(0) === '-' ? -1 : 1;
    pred = pred.replace(/^-/, '');
    return {
      getter: o => o[pred],
      descend: descending
    };
  });
  // schwartzian transform idiom implementation. aka: "decorate-sort-undecorate"
  return array.map(item => {
    return {
      src: item,
      compareValues: predicates.map(predicate => predicate.getter(item))
    };
  })
  .sort((o1, o2) => {
    let i = -1, result = 0;
    while (++i < predicates.length) {
      if (o1.compareValues[i] < o2.compareValues[i]) result = -1;
      if (o1.compareValues[i] > o2.compareValues[i]) result = 1;
      if (result *= predicates[i].descend) break;
    }
    return result;
  })
  .map(item => item.src);
}

Aqui está um exemplo de como usá-lo:

let games = [
  { name: 'Mashraki',          rating: 4.21 },
  { name: 'Hill Climb Racing', rating: 3.88 },
  { name: 'Angry Birds Space', rating: 3.88 },
  { name: 'Badland',           rating: 4.33 }
];

// sort by one attribute
console.log(sortByAttribute(games, 'name'));
// sort by mupltiple attributes
console.log(sortByAttribute(games, '-rating', 'name'));

14

Classificação (mais) Matrizes complexas de objetos

Como você provavelmente encontra estruturas de dados mais complexas como essa matriz, eu expandiria a solução.

TL; DR

São versões mais conectáveis ​​baseadas na resposta muito adorável do @ ege-Özcan .

Problema

Encontrei o abaixo e não consegui alterá-lo. Também não queria achatar o objeto temporariamente. Também não queria usar sublinhado / lodash, principalmente por razões de desempenho e diversão para implementá-lo.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

Objetivo

O objetivo é classificá-lo principalmente por People.Name.namee secundariamente porPeople.Name.surname

Obstáculos

Agora, na solução base, usa a notação de colchete para calcular as propriedades para classificar dinamicamente. Aqui, porém, teríamos que construir a notação de colchete dinamicamente também, já que você esperaria algo comoPeople['Name.name'] funcionassem - o que não.

Simplesmente fazer People['Name']['name'], por outro lado, é estático e só permite que você desça n nível ésimo.

Solução

A principal adição aqui será descer a árvore de objetos e determinar o valor da última folha, você deve especificar, bem como qualquer folha intermediária.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

People.sort(dynamicMultiSort(['Name','name'], ['Name', '-surname']));
// Results in...
// [ { Name: { name: 'AAA', surname: 'ZZZ' }, Middlename: 'Abrams' },
//   { Name: { name: 'Name', surname: 'Surname' }, Middlename: 'JJ' },
//   { Name: { name: 'Name', surname: 'AAA' }, Middlename: 'Wars' } ]

// same logic as above, but strong deviation for dynamic properties 
function dynamicSort(properties) {
  var sortOrder = 1;
  // determine sort order by checking sign of last element of array
  if(properties[properties.length - 1][0] === "-") {
    sortOrder = -1;
    // Chop off sign
    properties[properties.length - 1] = properties[properties.length - 1].substr(1);
  }
  return function (a,b) {
    propertyOfA = recurseObjProp(a, properties)
    propertyOfB = recurseObjProp(b, properties)
    var result = (propertyOfA < propertyOfB) ? -1 : (propertyOfA > propertyOfB) ? 1 : 0;
    return result * sortOrder;
  };
}

/**
 * Takes an object and recurses down the tree to a target leaf and returns it value
 * @param  {Object} root - Object to be traversed.
 * @param  {Array} leafs - Array of downwards traversal. To access the value: {parent:{ child: 'value'}} -> ['parent','child']
 * @param  {Number} index - Must not be set, since it is implicit.
 * @return {String|Number}       The property, which is to be compared by sort.
 */
function recurseObjProp(root, leafs, index) {
  index ? index : index = 0
  var upper = root
  // walk down one level
  lower = upper[leafs[index]]
  // Check if last leaf has been hit by having gone one step too far.
  // If so, return result from last step.
  if (!lower) {
    return upper
  }
  // Else: recurse!
  index++
  // HINT: Bug was here, for not explicitly returning function
  // https://stackoverflow.com/a/17528613/3580261
  return recurseObjProp(lower, leafs, index)
}

/**
 * Multi-sort your array by a set of properties
 * @param {...Array} Arrays to access values in the form of: {parent:{ child: 'value'}} -> ['parent','child']
 * @return {Number} Number - number for sort algorithm
 */
function dynamicMultiSort() {
  var args = Array.prototype.slice.call(arguments); // slight deviation to base

  return function (a, b) {
    var i = 0, result = 0, numberOfProperties = args.length;
    // REVIEW: slightly verbose; maybe no way around because of `.sort`-'s nature
    // Consider: `.forEach()`
    while(result === 0 && i < numberOfProperties) {
      result = dynamicSort(args[i])(a, b);
      i++;
    }
    return result;
  }
}

Exemplo

Exemplo de trabalho no JSBin


2
Por quê? Esta não é a resposta para a pergunta original e "o objetivo" pode ser resolvido simplesmente com People.sort ((a, b) => {retorne a.Name.name.localeCompare (b.Name.name) || a.Name .surname.localeCompare (b.Name.surname)})
Tero Tolonen

12

Mais uma opção:

var someArray = [...];

function generateSortFn(prop, reverse) {
    return function (a, b) {
        if (a[prop] < b[prop]) return reverse ? 1 : -1;
        if (a[prop] > b[prop]) return reverse ? -1 : 1;
        return 0;
    };
}

someArray.sort(generateSortFn('name', true));

classifica em ascensão por padrão.


1
Ligeiramente versão mudou para ordenação por múltiplos campos é aqui, se necessário: stackoverflow.com/questions/6913512/...
Ravshan Samandarov

parece que poderia ser o próximo: export function generateSortFn (prop: string, reverse: boolean = false): (... args: any) => number {return (a, b) => {return a [prop ] <b [prop]? reverter ? 1: -1: a [prop]> b [prop]? reverter ? -1: 1: 0; }; }
Den Kerny

Eu preferiria um código mais legível do que o menor.
Ravshan Samandarov

concordou, mas em alguns casos eu não tenho necessidade de olhar para as funções de utilidade.
Den Kerny

11

Uma função simples que classifica uma matriz de objeto por uma propriedade

function sortArray(array, property, direction) {
    direction = direction || 1;
    array.sort(function compare(a, b) {
        let comparison = 0;
        if (a[property] > b[property]) {
            comparison = 1 * direction;
        } else if (a[property] < b[property]) {
            comparison = -1 * direction;
        }
        return comparison;
    });
    return array; // Chainable
}

Uso:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

sortArray(objs, "last_nom"); // Asc
sortArray(objs, "last_nom", -1); // Desc

Essa solução funcionou perfeitamente para eu classificar bidirecional. Obrigado u
manjuvreddy 23/03

10

Uma maneira simples:

objs.sort(function(a,b) {
  return b.last_nom.toLowerCase() < a.last_nom.toLowerCase();
});

Veja que '.toLowerCase()'é necessário para evitar erros na comparação de cadeias.


4
Você pode usar a seta funções para deixar o código um pouco mais elegante:objs.sort( (a,b) => b.last_nom.toLowerCase() < a.last_nom.toLowerCase() );
Sertage

Isso está errado pelo mesmo motivo explicado aqui .
Patrick Roberts

2
As funções de seta não são dignas de ES5. Toneladas de motores ainda estão restritas ao ES5. No meu caso, acho que a resposta acima significativamente melhor desde que eu estou em um motor ES5 (forçado pela minha empresa)
dylanh724

9

parâmetros de desc adicionais para o código Ege Özcan

function dynamicSort(property, desc) {
    if (desc) {
        return function (a, b) {
            return (a[property] > b[property]) ? -1 : (a[property] < b[property]) ? 1 : 0;
        }   
    }
    return function (a, b) {
        return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
    }
}

9

Combinando a solução dinâmica do Ege com a ideia de Vinay, você obtém uma solução robusta e agradável:

Array.prototype.sortBy = function() {
    function _sortByAttr(attr) {
        var sortOrder = 1;
        if (attr[0] == "-") {
            sortOrder = -1;
            attr = attr.substr(1);
        }
        return function(a, b) {
            var result = (a[attr] < b[attr]) ? -1 : (a[attr] > b[attr]) ? 1 : 0;
            return result * sortOrder;
        }
    }
    function _getSortFunc() {
        if (arguments.length == 0) {
            throw "Zero length arguments not allowed for Array.sortBy()";
        }
        var args = arguments;
        return function(a, b) {
            for (var result = 0, i = 0; result == 0 && i < args.length; i++) {
                result = _sortByAttr(args[i])(a, b);
            }
            return result;
        }
    }
    return this.sort(_getSortFunc.apply(null, arguments));
}

Uso:

// Utility for printing objects
Array.prototype.print = function(title) {
    console.log("************************************************************************");
    console.log("**** "+title);
    console.log("************************************************************************");
    for (var i = 0; i < this.length; i++) {
        console.log("Name: "+this[i].FirstName, this[i].LastName, "Age: "+this[i].Age);
    }
}

// Setup sample data
var arrObj = [
    {FirstName: "Zach", LastName: "Emergency", Age: 35},
    {FirstName: "Nancy", LastName: "Nurse", Age: 27},
    {FirstName: "Ethel", LastName: "Emergency", Age: 42},
    {FirstName: "Nina", LastName: "Nurse", Age: 48},
    {FirstName: "Anthony", LastName: "Emergency", Age: 44},
    {FirstName: "Nina", LastName: "Nurse", Age: 32},
    {FirstName: "Ed", LastName: "Emergency", Age: 28},
    {FirstName: "Peter", LastName: "Physician", Age: 58},
    {FirstName: "Al", LastName: "Emergency", Age: 51},
    {FirstName: "Ruth", LastName: "Registration", Age: 62},
    {FirstName: "Ed", LastName: "Emergency", Age: 38},
    {FirstName: "Tammy", LastName: "Triage", Age: 29},
    {FirstName: "Alan", LastName: "Emergency", Age: 60},
    {FirstName: "Nina", LastName: "Nurse", Age: 54}
];

//Unit Tests
arrObj.sortBy("LastName").print("LastName Ascending");
arrObj.sortBy("-LastName").print("LastName Descending");
arrObj.sortBy("LastName", "FirstName", "-Age").print("LastName Ascending, FirstName Ascending, Age Descending");
arrObj.sortBy("-FirstName", "Age").print("FirstName Descending, Age Ascending");
arrObj.sortBy("-Age").print("Age Descending");

1
Obrigado pela ideia! A propósito, por favor, não incentive as pessoas a mudarem o Protótipo de Matriz (veja o aviso no final do meu exemplo).
Ege Özcan

8

Seguindo o seu exemplo, você precisa classificar por dois campos (sobrenome, nome) e não por um. Você pode usar a biblioteca Alasql para fazer esse tipo em uma linha:

var res = alasql('SELECT * FROM ? ORDER BY last_nom, first_nom',[objs]);

Experimente este exemplo em jsFiddle .


8
objs.sort(function(a,b){return b.last_nom>a.last_nom})

1
Na verdade, não parecia funcionar, tinha que usar a resposta aceita. Não estava classificando corretamente.
madprops

8

Dado o exemplo original:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

Classifique por vários campos:

objs.sort(function(left, right) {
    var last_nom_order = left.last_nom.localeCompare(right.last_nom);
    var first_nom_order = left.first_nom.localeCompare(right.first_nom);
    return last_nom_order || first_nom_order;
});

Notas

  • a.localeCompare(b)é universalmente suportado e retorna -1,0,1 se a<b, a==b, a>brespectivamente.
  • ||na última linha dá last_nomprioridade sobrefirst_nom .
  • Subtração funciona em campos numéricos: var age_order = left.age - right.age;
  • Negar para reverter a ordem, return -last_nom_order || -first_nom_order || -age_order;

8

Tente isso,

UPTO ES5

//Ascending Sort
items.sort(function (a, b) {
   return a.value - b.value;
});


//Descending Sort
items.sort(function (a, b) {
   return b.value - a.value;
});


IN ES6 & above:

// Ascending sort
items.sort((a, b) => a.value - b.value);

// Descending Sort
 items.sort((a, b) => b.value - a.value);

melhor e fácil solução
Omar Hasan

7

Pode ser necessário convertê-los para letras minúsculas para evitar confusões.

objs.sort(function (a,b) {

var nameA=a.last_nom.toLowerCase(), nameB=b.last_nom.toLowerCase()

if (nameA < nameB)
  return -1;
if (nameA > nameB)
  return 1;
return 0;  //no sorting

})

7
function compare(propName) {
    return function(a,b) {
        if (a[propName] < b[propName])
            return -1;
        if (a[propName] > b[propName])
            return 1;
        return 0;
    };
}

objs.sort(compare("last_nom"));

1
Considere editar sua postagem para adicionar mais explicações sobre o que seu código faz e por que ele resolverá o problema. Uma resposta que geralmente contém apenas código (mesmo que esteja funcionando) geralmente não ajuda o OP a entender seu problema.
Drenmi 29/10

7

Usando Ramda,

npm install ramda

import R from 'ramda'
var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];
var ascendingSortedObjs = R.sortBy(R.prop('last_nom'), objs)
var descendingSortedObjs = R.reverse(ascendingSortedObjs)

6

Este é um problema simples, não sei por que as pessoas têm uma solução tão complexa.
Uma função de classificação simples (baseada no algoritmo de classificação rápida):

function sortObjectsArray(objectsArray, sortKey)
        {
            // Quick Sort:
            var retVal;

            if (1 < objectsArray.length)
            {
                var pivotIndex = Math.floor((objectsArray.length - 1) / 2);  // middle index
                var pivotItem = objectsArray[pivotIndex];                    // value in the middle index
                var less = [], more = [];

                objectsArray.splice(pivotIndex, 1);                          // remove the item in the pivot position
                objectsArray.forEach(function(value, index, array)
                {
                    value[sortKey] <= pivotItem[sortKey] ?                   // compare the 'sortKey' proiperty
                        less.push(value) :
                        more.push(value) ;
                });

                retVal = sortObjectsArray(less, sortKey).concat([pivotItem], sortObjectsArray(more, sortKey));
            }
            else
            {
                retVal = objectsArray;
            }

            return retVal;
        }

Use exemplo:

var myArr = 
        [
            { val: 'x', idx: 3 },
            { val: 'y', idx: 2 },
            { val: 'z', idx: 5 },
        ];
myArr = sortObjectsArray(myArr, 'idx');

5
Como implementar a classificação rápida em js é uma solução simples? Algoritmo simples, mas não uma solução simples.
23815 Andrew Andrew

É simples, pois não usa bibliotecas externas e não altera o protótipo do objeto. Na minha opinião, o comprimento do código não tem impacto direto sobre a complexidade do código
Gil Epshtain

2
Bem, deixe-me tentar com palavras diferentes: como reinventar a roda é uma solução simples?
Roberto14
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.