Ordenar matriz de objetos por valor de propiedad de cadena

2764

Tengo una matriz de objetos JavaScript:

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

¿Cómo puedo ordenarlos por el valor de last_nom en JavaScript?

Lo sé sort(a,b), pero eso solo parece funcionar en cadenas y números. ¿Necesito agregar un toString()método a mis objetos?

Tyrone Slothrop
fuente
77
Este script le permite hacer exactamente eso a menos que quiera escribir su propia función de comparación o clasificador: thomasfrank.se/sorting_things.html
Baversjo
la forma más rápida es usar el módulo de ordenamiento isomorfo que funciona de forma nativa tanto en el navegador como en el nodo, que admite cualquier tipo de entrada, campos calculados y órdenes de clasificación personalizadas.
Lloyd

Respuestas:

3923

Es bastante fácil escribir su propia función de comparación:

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

O en línea (c / o Marco Demaio):

objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0)); 
Wogan
fuente
441
O en línea: 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)funcionará también.
Cerbrus
146
para aquellos que buscan un tipo donde el campo es numérico, el cuerpo de la función de comparación: return a.value - b.value;(ASC)
Andre Figueiredo
20
@Cerbrus localeComparees importante cuando se usan caracteres acentuados en idiomas extranjeros, y también es más elegante.
Marcos Lima
839

También puede crear una función de ordenación dinámica que clasifique los objetos por el valor que pasa:

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

Entonces puede tener una variedad de objetos como este:

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

... y funcionará cuando lo hagas:

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

En realidad, esto ya responde a la pregunta. La parte de abajo está escrita porque muchas personas me contactaron, quejándose de que no funciona con múltiples parámetros .

Parámetros Múltiples

Puede usar la siguiente función para generar funciones de clasificación con múltiples parámetros de clasificación.

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

Lo que te permitiría hacer algo como esto:

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

Matriz de subclasificación

Para los afortunados entre nosotros que pueden usar ES6, lo que permite extender los objetos nativos:

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

Eso permitiría esto:

MyArray.from(People).sortBy("Name", "-Surname");
Ege Özcan
fuente
Tenga en cuenta que los nombres de propiedades en JavaScript pueden ser cualquier cadena y si tiene propiedades que comienzan con un "-" (extremadamente improbable y probablemente no es una buena idea), deberá modificar la función dynamicSort para usar algo más como una ordenación inversa indicador.
Ege Özcan
Me di cuenta de que dynamicSort()en el ejemplo anterior colocará letras mayúsculas delante de las minúsculas. Por ejemplo, si tengo los valores APd, Akliny Abe- los resultados en un ASC tipo debe ser Abe, Aklin, APd. Sin embargo, con su ejemplo, los resultados son APd, Abe, Aklin. De todos modos para corregir este comportamiento?
Lloyd Banks
66
@LloydBanks si está usando esto estrictamente para cadenas, entonces puede usarlo en var result = a[property].localeCompare(b[property]);lugar de var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;.
Ege Özcan
1
@ EgeÖzcan Debería colocar elementos en la parte superior o inferior, aquí está la implementación que terminé usando pastebin.com/g4dt23jU
dzh
1
Esta es una gran solución, pero tiene un problema si compara números. Agregue esto antes de verificar:if( !isNaN(a[property]) ) a[property] = Number(a[property]); if( !isNaN(b[property]) ) b[property] = Number(b[property]);
Ivijan Stefan Stipić el
417

En ES6 / ES2015 o posterior, puede hacer lo siguiente:

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

Antes de ES6 / ES2015

objs.sort(function(a, b) {
    return a.last_nom.localeCompare(b.last_nom)
});
Vlad Bezden
fuente
29
esto ha estado disponible desde JS 1.1, la pieza de flecha gruesa para esto es la pieza ES6 / 2015. Pero sigue siendo muy útil, y la mejor respuesta en mi opinión
Jon Harding
13
@PratikKelwalkar: si necesita invertirlo, simplemente cambie la comparación ayb: objs.sort ((a, b) => b.last_nom.localeCompare (a.last_nom));
Vlad Bezden
¿es posible usar un índice también para direccionar el campo para ordenar: en lugar de last_nomusar solo el número en la matriz 1:?
y-bri
44
@VladBezden gracias por tu respuesta! Esta solución es la primera con un esfuerzo programático muy pequeño y resultados de clasificación correctos y una matriz de cadenas como: ["Nombre1", "Nombre10", "Nombre2", "otra cosa", "Nombre11"]. objs.sort((a, b) => a.last_nom.localeCompare(b.last_nom, undefined, {numberic: true}));
Obtuve la
1
Para hacer esto con números descendentes: .sort ((a, b) => b.numberProperty - a.numberProperty). Ascendente: .sort ((a, b) => a.numberProperty - b.numberProperty)
Sebastian Patten
188

underscore.js

use subrayado, es pequeño e impresionante ...

sortBy_.sortBy (list, iterator, [context]) Devuelve una copia ordenada de la lista, clasificada en orden ascendente por los resultados de ejecutar cada valor a través del iterador. El iterador también puede ser el nombre de la cadena de la propiedad para ordenar (por ejemplo, longitud).

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' );
David Morrow
fuente
18
David, ¿podría modificar la respuesta decir, var sortedObjs = _.sortBy( objs, 'first_nom' );. objsserá no ser clasificado como un resultado de esto. La función devolverá una matriz ordenada. Eso lo haría más explícito.
Jess el
10
Para revertir la clasificación:var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse();
Erdal G.
2
debe cargar el "subrayado" de la biblioteca de JavaScript:<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"> </script>
y-bri
55
También disponible Lodashpara aquellos que prefieren ese
WoJ
2
En general, esto sería lo mismo: var sortedObjs = _.sortBy( objs, 'first_nom' );o si lo desea en un orden diferente:var sortedObjs = _.orderBy( objs, ['first_nom'],['dsc'] );
Travis Heeter
186

No entiendo por qué la gente lo hace tan complicado:

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

Para motores más estrictos:

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

Cambie el operador para ordenarlo por orden alfabético inverso.

p3lim
fuente
17
En realidad, esto no es correcto ya que la función utilizada en sort debería devolver -1, 0 o 1, pero la función anterior devuelve un valor booleano. El tipo funciona bien en Chrome pero falla en PhantomJS, por ejemplo. Ver code.google.com/p/phantomjs/issues/detail?id=1090
schup
Algunos motores explican la estupidez, de esta manera fue como explotar eso. He actualizado mi respuesta con una versión adecuada.
p3lim
19
Sugeriría editar para sacar la primera versión. Es más sucinto, por lo que se ve más atractivo, pero no funciona, al menos de manera no confiable. Si alguien lo prueba en un navegador y funciona, es posible que ni siquiera se den cuenta de que tienen un problema (especialmente si no han leído los comentarios). La segunda versión funciona correctamente, por lo que realmente no hay necesidad de la primera.
Kate
2
@Simon Realmente aprecié tener primero la versión "ligeramente incorrecta", ya que la implementación más estricta demora unos segundos en analizar y comprender, y sería mucho más difícil sin ella.
Aaron Sherman
1
@ Lion789 solo haz esto:if(a.count == b.count) return a.name > b.name; else return a.count > b.count;
p3lim
62

Si tiene apellidos duplicados, puede ordenarlos por nombre-

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;
});
Kennebec
fuente
@BadFeelingAboutThis ¿Qué significa devolver -1 o 1? Entiendo que -1 literalmente significa que A es menor que B solo por la sintaxis, pero ¿por qué usar un 1 o -1? Veo que todos están usando esos números como valores de retorno, pero ¿por qué? Gracias.
Chris22
1
@ Chris22 un número negativo devuelto significa que bdebería aparecer después aen la matriz. Si se devuelve un número positivo, significa que adebe venir después b. Si 0se devuelve, significa que se consideran iguales. Siempre puede leer la documentación: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
BadFeelingAboutThis
@BadFeelingAboutThis gracias, por la explicación y el enlace. Lo creas o no, busqué en Google varios fragmentos de código 1, 0, -1antes de preguntar esto aquí. Simplemente no estaba encontrando la información que necesitaba.
Chris22
48

Solución simple y rápida a este problema utilizando la herencia del prototipo:

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

Ejemplo / 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"}]

Actualización: ya no modifica la matriz original.

Vinay Aggarwal
fuente
55
No solo devuelve otra matriz. pero en realidad clasifica el original!
Vinay Aggarwal
Si desea asegurarse de que está utilizando una ordenación natural con números (es decir, 0,1,2,10,11, etc.) use parseInt con el conjunto Radix. developer.mozilla.org/en-US/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 Gracias por corregirlo. pero supongo que en lugar de hacer una función de clasificación para hacer esta sensibilización, es mejor si hacemos una función separada para corregir los tipos de datos. Porque la función de clasificación no puede decir qué propiedad contendrá qué tipo de datos. :)
Vinay Aggarwal
Muy agradable. Invierta las flechas para obtener el efecto asc / desc.
Abdul Sadik Yalcin
44
nunca, nunca sugiera una solución que modifique el prototipo de un objeto central
pbanka
39

A partir de 2018 hay una solución mucho más corta y elegante. Solo usa. Array.prototype.sort () .

Ejemplo:

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;
});
0 pierna
fuente
77
En la pregunta, las cadenas se utilizaron para la comparación en oposición a los números. Su respuesta funciona muy bien para ordenar por números, pero no es tan buena para las comparaciones por cadena.
smcstewart
Los a.value - b.valueutilizados para comparar los atributos del objeto ( números en este caso) se pueden adoptar para los distintos tiempos de datos. Por ejemplo, regex se puede usar para comparar cada par de cadenas vecinas .
0leg
1
@ 0leg Me gustaría ver un ejemplo aquí del uso de expresiones regulares para comparar cadenas de esta manera.
Bob Stein
Esta implementación es bastante buena si necesita ordenarla por ID. Sí, ha sugerido usar regex para comparar cadenas vecinas, lo que hace que la solución sea más complicada, mientras que el propósito de esta versión simplificada será de otra manera si se usa regex junto con la solución dada. La simplicidad es lo mejor.
surendrapanday
33

Antigua respuesta que no es correcta:

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

ACTUALIZAR

Del comentario de Beauchamp:

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

Formato más legible:

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

Sin ternar anidados:

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

Explicación: Number()lanzará truehacia 1y falsehacia 0.

Damjan Pavlica
fuente
Funciona, pero el resultado es inestable por alguna razón
TitanFighter
@ AO17 no, no lo hará. No puedes restar cadenas.
Patrick Roberts
15
Esto debería hacerlo:arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))
Jean-François Beauchamp
3
@ Jean-FrançoisBeauchamp, su solución funciona perfectamente bien y mucho mejor.
Ahsan
¡el tercero con Number es sencillo y agradable!
Kojo
29

En lugar de utilizar una función de comparación personalizada, también puede crear un tipo de objeto con un toString()método personalizado (que es invocado por la función de comparación predeterminada):

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();
Christoph
fuente
29

Lodash.js (superconjunto de Underscore.js )

Es bueno no agregar un marco para cada lógica simple, pero confiar en marcos de utilidad bien probados puede acelerar el desarrollo y reducir la cantidad de errores.

Lodash produce código muy limpio y promueve un estilo de programación más funcional . En un vistazo, queda claro cuál es la intención del código.

El problema de OP simplemente se puede resolver como:

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

¿Más información? Por ejemplo, tenemos el siguiente objeto anidado:

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

Ahora podemos usar la abreviatura _.propertyuser.age para especificar la ruta a la propiedad que debe coincidir. Ordenaremos los objetos de usuario por la propiedad de edad anidada. Sí, permite la coincidencia de propiedades anidadas.

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

¿Quieres revertirlo? No hay problema. Use _.reverse .

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

¿Quieres combinar ambos usando cadena ?

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

¿O cuándo prefieres el flujo sobre la cadena?

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

Puedes usar

La forma más fácil: Lodash

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

Este método es como _.sortBy, excepto que permite especificar el orden de clasificación de los iterados para ordenar. Si las órdenes no están especificadas, todos los valores se ordenan en orden ascendente. De lo contrario, especifique un orden de "desc" para descendente o "asc" para el orden de clasificación ascendente de los valores correspondientes.

Argumentos

colección (Array | Object): la colección para iterar. [iteratees = [_. identity]] (Array [] | Function [] | Object [] | string []): Los iteratees para ordenar. [pedidos] (cadena []): el orden de los iterados.

Devoluciones

(Matriz): devuelve la nueva matriz ordenada.


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']);
Harvey
fuente
23

Aquí hay muchas buenas respuestas, pero me gustaría señalar que se pueden extender de manera muy simple para lograr una clasificación mucho más compleja. Lo único que tiene que hacer es usar el operador OR para encadenar funciones de comparación como esta:

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

Donde fn1, fn2... son las funciones de clasificación que devuelven [-1,0,1]. Esto da como resultado "ordenar por fn1", "ordenar por fn2", que es prácticamente igual a ORDER BY en SQL.

Esta solución se basa en el comportamiento del ||operador que evalúa la primera expresión evaluada que se puede convertir en verdadera .

La forma más simple tiene solo una función en línea como esta:

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

Teniendo dos pasos con last_nom, el first_nomorden de clasificación se vería así:

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

Una función de comparación genérica podría ser algo como esto:

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

Esta función podría ampliarse para admitir campos numéricos, sensibilidad de mayúsculas y minúsculas, tipos de datos arbitrarios, etc.

Puedes usarlos encadenándolos por orden de prioridad:

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

El punto aquí es que JavaScript puro con enfoque funcional puede llevarlo lejos sin bibliotecas externas o código complejo. También es muy efectivo, ya que no es necesario analizar las cadenas.

Tero Tolonen
fuente
23

Ejemplo de uso:

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

Guión:

/**
 * @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;
  };
};
Jamie Mason
fuente
gracias por desglosar esto, estoy tratando de entender por qué 1, 0, -1se utilizan dígitos para ordenar. Incluso con su explicación anterior, que se ve muy bien, todavía no lo entiendo del todo. Siempre pienso -1en cuando uso la propiedad de longitud de matriz, es decir: arr.length = -1significa que el elemento no se encuentra. Probablemente estoy mezclando cosas aquí, pero ¿podría ayudarme a entender por qué 1, 0, -1se utilizan los dígitos para determinar el orden? Gracias.
Chris22
1
Esto no es del todo exacto, pero podría ayudar pensarlo así: la función pasada a array.sort se llama una vez para cada elemento de la matriz, como el argumento llamado "a". El valor de retorno de cada llamada de función es cómo debe alterarse el índice (número de posición actual) del elemento "a" en comparación con el siguiente elemento "b". El índice dicta el orden de la matriz (0, 1, 2, etc.) Entonces, si "a" está en el índice 5 y devuelve -1, entonces 5 + -1 == 4 (muévalo más cerca del frente) 5 + 0 == 5 (manténgalo donde está), etc. Recorre la matriz comparando 2 vecinos cada vez hasta que llega al final, dejando una matriz ordenada.
Jamie Mason el
Gracias por tomarse el tiempo para explicar esto más a fondo. Entonces, usando su explicación y el MDN Array.prototype.sort , le diré lo que pienso de esto: en comparación con ay b, si aes mayor que bagregue 1 al índice de ay colóquelo detrás b, si aes menor que b, reste 1 de ay colóquelo delante de b. Si ay bson iguales, agregue 0 a ay déjelo donde está.
Chris22
22

No he visto este enfoque en particular sugerido, así que aquí hay un método de comparación breve que me gusta usar que funciona para ambos stringy 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));

Aquí hay una explicación de sortBy():

sortBy()acepta un fnque selecciona qué valor de un objeto usar como comparación y devuelve una función a la que se puede pasar directamente Array.prototype.sort(). En este ejemplo, estamos utilizando o.last_nomcomo valor de comparación, por lo que cada vez que recibimos a través de dos objetos Array.prototype.sort()tales como

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

y

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

usamos

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

para compararlos

Recordando eso fn = o => o.last_nom, podemos expandir la función de comparación al equivalente

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

El ||operador lógico OR tiene una funcionalidad de cortocircuito que es muy útil aquí. Debido a cómo funciona, el cuerpo de la función anterior significa

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

Como una ventaja adicional, aquí está el equivalente en ECMAScript 5 sin funciones de flecha, que desafortunadamente es más detallado:

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

Patrick Roberts
fuente
17

Sé que esta pregunta es demasiado antigua, pero no vi ninguna implementación similar a la mía.
Esta versión se basa en el modismo de transformación de Schwartz .

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

Aquí hay un ejemplo de cómo usarlo:

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'));
a8m
fuente
14

Ordenar (más) matrices complejas de objetos

Como probablemente encuentre estructuras de datos más complejas como esta matriz, ampliaría la solución.

TL; DR

Son versiones más enchufables basadas en la muy encantadora respuesta de @ege-Özcan .

Problema

Encontré lo siguiente y no pude cambiarlo. Tampoco quería aplanar el objeto temporalmente. Tampoco quería usar el subrayado / lodash, principalmente por razones de rendimiento y la diversión de implementarlo yo mismo.

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

Objetivo

El objetivo es ordenarlo principalmente por People.Name.name y en segundo lugar porPeople.Name.surname

Los obstáculos

Ahora, en la solución base usa la notación de corchetes para calcular las propiedades para ordenar dinámicamente. Aquí, sin embargo, también tendríamos que construir dinámicamente la notación de corchetes, ya que esperaría que algunosPeople['Name.name'] funcionen, lo que no funciona.

Simplemente hacer People['Name']['name'], por otro lado, es estático y solo te permite bajar el n nivel -ésimo.

Solución

La adición principal aquí será caminar por el árbol de objetos y determinar el valor de la última hoja, debe especificar, así como cualquier hoja intermedia.

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

Ejemplo

Ejemplo de trabajo en JSBin

eljefedelrodeodeljefe
fuente
2
¿Por qué? Esta no es la respuesta a la pregunta original y "el objetivo" podría resolverse simplemente con People.sort ((a, b) => {return a.Name.name.localeCompare (b.Name.name) || a.Name .surname.localeCompare (b.Name.surname)})
Tero Tolonen
12

Una opción mas:

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

ordena ascendente por defecto.

Ravshan Samandarov
fuente
1
La versión ligeramente modificada para ordenar por múltiples campos está aquí si es necesario: stackoverflow.com/questions/6913512/…
Ravshan Samandarov
parece que podría ser el siguiente: función de exportación generateSortFn (prop: string, reverse: boolean = false): (... args: any) => number {return (a, b) => {return a [prop ] <b [prop]? contrarrestar ? 1: -1: a [prop]> b [prop]? contrarrestar ? -1: 1: 0; }; }
Den Kerny
Preferiría un código más legible que el más corto.
Ravshan Samandarov
de acuerdo, pero en algunos casos no tengo necesidad de mirar las funciones de utilidad.
Den Kerny
11

Una función simple que ordena una matriz de objetos por una propiedad

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
Francois Girard
fuente
Esta solución funcionó perfectamente para mí para ordenar bidireccional. Gracias
manjuvreddy
10

Una manera simple:

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

Ver que '.toLowerCase()'es necesario para evitar errores en la comparación de cadenas.

Caio Ladislau
fuente
44
Puede usar las funciones de flecha para que el código sea un poco más elegante:objs.sort( (a,b) => b.last_nom.toLowerCase() < a.last_nom.toLowerCase() );
Sertage
Esto está mal por la misma razón que se explica aquí .
Patrick Roberts
2
Las funciones de flecha no son dignas de ES5. Toneladas de motores todavía están restringidas a ES5. En mi caso, encuentro la respuesta anterior significativamente mejor ya que estoy en un motor ES5 (forzado por mi compañía)
dylanh724
9

parámetros desc adicionales para el 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;
    }
}
Behnam Shomali
fuente
9

Combinando la solución dinámica de Ege con la idea de Vinay, obtienes una buena solución robusta:

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");
Mike R
fuente
1
Gracias por la idea! Por cierto, no aliente a las personas a cambiar el prototipo de matriz (vea la advertencia al final de mi ejemplo).
Ege Özcan
8

Según su ejemplo, debe ordenar por dos campos (apellido, nombre), en lugar de uno. Puede usar la biblioteca Alasql para hacer este tipo en una línea:

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

Pruebe este ejemplo en jsFiddle .

agershun
fuente
8
objs.sort(function(a,b){return b.last_nom>a.last_nom})
Roshni Bokade
fuente
1
En realidad no parecía funcionar, tenía que usar la respuesta aceptada. No estaba ordenando correctamente.
madprops
8

Dado el ejemplo original:

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

Ordenar por múltiples 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)está universalmente compatible y vuelve -1,0,1 si a<b, a==b, a>brespectivamente.
  • ||en la última línea da last_nomprioridad sobre first_nom.
  • La resta funciona en campos numéricos: var age_order = left.age - right.age;
  • Negar al orden inverso, return -last_nom_order || -first_nom_order || -age_order;
Bob Stein
fuente
8

Prueba esto,

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);
Abhishek
fuente
mejor y más fácil solución
Omar Hasan
7

Es posible que deba convertirlos a minúsculas para evitar confusiones.

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

})
Burak Keceli
fuente
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"));
Evgenii
fuente
1
Considere editar su publicación para agregar más explicaciones sobre lo que hace su código y por qué resolverá el problema. Una respuesta que en su mayoría solo contiene código (incluso si está funcionando) generalmente no ayudará al OP a comprender su problema.
Drenmi
7

Usando Ramda,

npm instalar 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)
Sridhar Sg
fuente
6

Este es un problema simple, no sé por qué las personas tienen una solución tan compleja.
Una función de clasificación simple (basada en el algoritmo de clasificación 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 ejemplo:

var myArr = 
        [
            { val: 'x', idx: 3 },
            { val: 'y', idx: 2 },
            { val: 'z', idx: 5 },
        ];
myArr = sortObjectsArray(myArr, 'idx');
Gil Epshtain
fuente
55
¿Cómo implementar una ordenación rápida en js es una solución simple? Algoritmo simple pero no una solución simple.
Andrew
Es simple ya que no usa ninguna biblioteca externa y no cambia el prototipo del objeto. En mi opinión, la longitud del código no tiene un impacto directo en la complejidad del código
Gil Epshtain
2
Bueno, déjenme intentarlo con diferentes palabras: ¿Cómo reinventar la rueda es una solución simple?
Roberto14