Ordina la matrice di oggetti in base al valore della proprietà stringa


2763

Ho una serie di oggetti JavaScript:

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

Come posso ordinarli in base al valore di last_nomin JavaScript?

Lo so sort(a,b), ma sembra funzionare solo su stringhe e numeri. Devo aggiungere un toString()metodo ai miei oggetti?


7
Questo script ti consente di fare proprio questo a meno che tu non voglia scrivere la tua funzione di confronto o selezionatore: thomasfrank.se/sorting_things.html
Baversjo,

il modo più rapido è utilizzare il modulo di ordinamento-array isomorfo che funziona nativamente sia nel browser che nel nodo, supportando qualsiasi tipo di input, campi calcolati e ordinamenti personalizzati.
Lloyd,

Risposte:


3923

È abbastanza facile scrivere la tua funzione di confronto:

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 in linea (c / o Marco Demaio):

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

441
O in linea: objs.sort (funzione (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)funzionerà anche.
Cerbrus,

146
per chi cerca una specie in cui il campo è numerico, il corpo della funzione di confronto: return a.value - b.value;(ASC)
Andre Figueiredo

20
@Cerbrus localeCompareè importante quando si usano caratteri accentati in lingue straniere e anche più elegante.
Marcos Lima,

839

Puoi anche creare una funzione di ordinamento dinamico che ordina gli oggetti in base al loro valore che passi:

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

Quindi puoi avere una serie di oggetti come questo:

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

... e funzionerà quando fai:

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

In realtà questo già risponde alla domanda. La parte inferiore è scritta perché molte persone mi hanno contattato, lamentandosi del fatto che non funziona con più parametri .

Parametri multipli

È possibile utilizzare la funzione seguente per generare funzioni di ordinamento con più parametri di ordinamento.

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

Che ti permetterebbe di fare qualcosa del genere:

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

Matrice di sottoclasse

Per i fortunati tra noi che possono usare ES6, che consente di estendere gli oggetti nativi:

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

Ciò consentirebbe questo:

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

Tieni presente che i nomi delle proprietà in JavaScript possono essere qualsiasi stringa e se hai proprietà che iniziano con un "-" (estremamente improbabile e probabilmente non una buona idea), dovrai modificare la funzione dynamicSort per usare qualcos'altro come ordinamento inverso indicatore.
Ege Özcan,

Ho notato che dynamicSort()nell'esempio di cui sopra metterà le lettere maiuscole davanti alle lettere minuscole. Per esempio, se ho i valori APd, Aklin, e Abe- i risultati in un ASC specie dovrebbe essere Abe, Aklin, APd. Ma con il tuo esempio, i risultati sono APd, Abe, Aklin. Comunque per correggere questo comportamento?
Lloyd Banks,

6
@LloydBanks se lo usi rigorosamente per le stringhe, puoi var result = a[property].localeCompare(b[property]);invece usarlo var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;.
Ege Özcan,

1
@ EgeÖzcan Dovrebbe mettere gli elementi in alto o in basso, ecco l'implementazione che ho finito usando pastebin.com/g4dt23jU
dzh

1
Questa è un'ottima soluzione ma hai un problema se confronti i numeri. Si prega di aggiungere questo prima di controllare:if( !isNaN(a[property]) ) a[property] = Number(a[property]); if( !isNaN(b[property]) ) b[property] = Number(b[property]);
Ivijan Stefan Stipić

416

In ES6 / ES2015 o versioni successive puoi procedere in questo modo:

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

Prima di ES6 / ES2015

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

29
questo è disponibile da JS 1.1, il pezzo di freccia grassa per questo è il pezzo ES6 / 2015. Ma comunque molto utile, e la migliore risposta secondo me
Jon Harding,

13
@PratikKelwalkar: se hai bisogno di invertire, basta cambiare il confronto aeb: objs.sort ((a, b) => b.last_nom.localeCompare (a.last_nom));
Vlad Bezden,

è possibile utilizzare anche un indice per indirizzare il campo per l'ordinamento: invece di last_nomusare solo il numero nell'array 1:?
and-bri

4
@VladBezden grazie per la tua risposta! Questa soluzione è la prima con un piccolo sforzo programmatico e risultati di ordinamento corretti e una matrice di stringhe come: ["Nome1", "Nome10", "Nome2", "qualcos'altro", "Nome11"]. Ho avuto l'ordinamento per funzionare correttamente conobjs.sort((a, b) => a.last_nom.localeCompare(b.last_nom, undefined, {numberic: true}));
scipper

1
Per fare ciò con i numeri decrescenti: .sort ((a, b) => b.numberProperty - a.numberProperty). Crescente: .sort ((a, b) => a.numberProprietà - b.numberProprietà)
Sebastian Patten

188

underscore.js

usa il trattino basso, è piccolo e fantastico ...

sortBy_.sortBy (elenco, iteratore, [contesto]) Restituisce una copia ordinata dell'elenco, ordinata in ordine crescente in base ai risultati dell'esecuzione di ciascun valore tramite iteratore. Iteratore può anche essere il nome stringa della proprietà da ordinare (ad es. Lunghezza).

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, potrebbe modificare la risposta per dire, var sortedObjs = _.sortBy( objs, 'first_nom' );. objsdi conseguenza non verrà ordinato da solo. La funzione restituirà un array ordinato. Ciò lo renderebbe più esplicito.
Jess,

10
Per invertire l'ordinamento:var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse();
Erdal G.

2
è necessario caricare la "sottolineatura" della libreria javascript:<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"> </script>
and-bri

5
Disponibile anche Lodashper quelli che preferiscono quello
WoJ

2
In lodash questo sarebbe lo stesso: var sortedObjs = _.sortBy( objs, 'first_nom' );o se lo vuoi in un ordine diverso:var sortedObjs = _.orderBy( objs, ['first_nom'],['dsc'] );
Travis Heeter,

186

Non capisco perché le persone lo rendono così complicato:

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

Per motori più severi:

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

Scambia l'operatore per ordinarlo in ordine alfabetico inverso.


17
Questo in realtà non è corretto in quanto la funzione utilizzata nell'ordinamento dovrebbe restituire -1, 0 o 1 ma la funzione precedente restituisce un valore booleano. L'ordinamento funziona bene in Chrome ma non riesce ad esempio in PhantomJS. Vedi code.google.com/p/phantomjs/issues/detail?id=1090
schup

Alcuni motori spiegano la stupidità, in questo modo è stato sfruttarlo. Ho aggiornato la mia risposta con una versione corretta.
p3lim,

19
Suggerirei la modifica per eliminare la prima versione. È più succinto, quindi sembra più attraente, ma non funziona, almeno non in modo affidabile. Se qualcuno lo prova in un browser e funziona, potrebbe anche non rendersi conto di avere un problema (soprattutto se non hanno letto i commenti). La seconda versione funziona correttamente, quindi non è davvero necessario per la prima.
Kate,

2
@Simon Ho apprezzato molto avere prima la versione "leggermente sbagliata", poiché l'implementazione più rigorosa richiede alcuni secondi per analizzare e comprendere e senza di essa sarebbe molto più difficile.
Aaron Sherman,

1
@ Lion789 basta fare questo:if(a.count == b.count) return a.name > b.name; else return a.count > b.count;
p3lim

62

Se hai cognomi duplicati puoi ordinarli per 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;
});

@BadFeelingAboutChe cosa significa restituire -1 o 1? Capisco che -1 significa letteralmente che A è inferiore a B solo per la sintassi, ma perché usare un 1 o -1? Vedo che tutti usano questi numeri come valori di ritorno, ma perché? Grazie.
Chris22,

1
@ Chris22 un numero negativo restituito significa che bdovrebbe venire dopo anell'array. Se viene restituito un numero positivo, significa che adovrebbe venire dopo b. Se 0viene restituito, significa che sono considerati uguali. Puoi sempre leggere la documentazione: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
BadFeelingAboutQuesto

@BadFeelingAboutQuesto grazie, per la spiegazione e il link. Che ci crediate o no, ho cercato su Google vari frammenti di codice usando il 1, 0, -1prima di chiedere questo qui. Non trovavo le informazioni di cui avevo bisogno.
Chris22,

48

Soluzione semplice e rapida a questo problema utilizzando l'ereditarietà 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;
  });
}

Esempio / Utilizzo

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

Aggiornamento: non modifica più l'array originale.


5
Non solo restituisce un altro array. ma ordina effettivamente quello originale !.
Vinay Aggarwal,

Se vuoi assicurarti di usare un ordinamento naturale con numeri (es. 0,1,2,10,11 ecc ...) usa parseInt con il set Radix. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… così: return (parseInt (a [p], 10)> parseInt (b [p], 10))? 1: (parseInt (a [p], 10) <parseInt (b [p], 10))? -1: 0;
Paolo,

@codehuntr Grazie per averlo corretto. ma suppongo che invece di fare la funzione di ordinamento per fare questa sensibilizzazione, è meglio se creiamo una funzione separata per correggere i tipi di dati. Perché la funzione di ordinamento non può non dire quale proprietà conterrà quale tipo di dati. :)
Vinay Aggarwal,

Molto bella. Invertire le frecce per ottenere l'effetto asc / desc.
Abdul Sadik Yalcin,

4
mai e poi mai suggerire una soluzione che modifica il prototipo di un oggetto principale
pbanka

39

A partire dal 2018 esiste una soluzione molto più corta ed elegante. Basta usare. Array.prototype.sort () .

Esempio:

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
Nella domanda le stringhe sono state utilizzate per il confronto anziché per i numeri. La tua risposta funziona benissimo per l'ordinamento per numero, ma non è così buona per i confronti per stringa.
smcstewart,

L' a.value - b.valueusato per confrontare gli attributi dell'oggetto ( numeri in questo caso) può essere adottato per le varie volte dei dati. Ad esempio, regex può essere utilizzato per confrontare ogni coppia delle stringhe vicine .
0leg

1
@ 0leg Vorrei vedere un esempio qui di come usare regex per confrontare le stringhe in questo modo.
Bob Stein,

Questa implementazione è abbastanza buona se devi ordinarla per ID. Sì, hai suggerito di usare regex per confrontare le stringhe vicine, il che rende la soluzione più complicata, mentre lo scopo di questa versione semplificata sarebbe altrimenti se regex fosse usato insieme a una data soluzione. La semplicità è la migliore.
surendrapanday,

33

Vecchia risposta che non è corretta:

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

AGGIORNARE

Dal commento di Beauchamp:

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

Formato più leggibile:

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

Senza ternari nidificati:

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

Spiegazione: Number()eseguirà il cast trueda 1e falseverso 0.


Funziona, ma il risultato è instabile per qualche motivo
TitanFighter

@ AO17 no. Non puoi sottrarre stringhe.
Patrick Roberts,

15
Questo dovrebbe farlo:arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))
Jean-François Beauchamp,

3
@ Jean-FrançoisBeauchamp, la tua soluzione funziona perfettamente e molto meglio.
Ahsan,

il terzo con il numero è semplice e carino!
Kojo

29

Invece di utilizzare una funzione di confronto personalizzata, è anche possibile creare un tipo di oggetto con il toString()metodo personalizzato (che viene richiamato dalla funzione di confronto predefinita):

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 (superset di Underscore.js )

È bene non aggiungere un framework per ogni semplice logica, ma fare affidamento su framework di utilità ben collaudati può accelerare lo sviluppo e ridurre la quantità di bug.

Lodash produce un codice molto pulito e promuove uno stile di programmazione più funzionale . In un colpo d'occhio diventa chiaro quale sia l'intento del codice.

Il problema di OP può essere semplicemente risolto come:

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

Ulteriori informazioni? Ad esempio abbiamo il seguente oggetto nidificato:

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

Ora possiamo usare il _.property scorciatoia user.ageper specificare il percorso della proprietà che dovrebbe essere abbinato. Ordineremo gli oggetti utente in base alla proprietà age nidificata. Sì, consente la corrispondenza delle proprietà nidificate!

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

Lo vuoi invertire? Nessun problema. Usa _.reverse .

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

Vuoi combinare entrambi usando la catena ?

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

O quando preferisci il flusso sopra la catena

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

28

Puoi usare

Il modo più semplice: Lodash

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

Questo metodo è simile a _.sortBy, tranne per il fatto che consente di specificare gli ordinamenti degli iterati per i quali ordinare. Se gli ordini non sono specificati, tutti i valori vengono ordinati in ordine crescente. Altrimenti, specificare un ordine di "desc" per decrescente o "asc" per l'ordinamento crescente di valori corrispondenti.

argomenti

collection (Array | Object): la collezione su cui scorrere. [iteratees = [_. identity]] (Array [] | Function [] | Object [] | string []): gli iterati da ordinare. [ordini] (stringa []): gli ordinamenti degli iterati.

ritorna

(Array): restituisce il nuovo array ordinato.


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

Ci sono molte buone risposte qui, ma vorrei sottolineare che possono essere estese in modo molto semplice per ottenere un ordinamento molto più complesso. L'unica cosa che devi fare è utilizzare l'operatore OR per concatenare funzioni di confronto come questa:

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

Dove fn1, fn2, ... sono le funzioni di ordinamento, che di ritorno [-1,0,1]. Ciò si traduce in "ordinamento per fn1", "ordinamento per fn2" che è praticamente uguale a ORDER BY in SQL.

Questa soluzione si basa sul comportamento ||dell'operatore che valuta la prima espressione valutata che può essere convertita in vero .

La forma più semplice ha una sola funzione integrata come questa:

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

Avendo due passaggi last_nom, l' first_nomordinamento sarebbe simile al seguente:

// 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 funzione di confronto generico potrebbe essere qualcosa del genere:

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

Questa funzione potrebbe essere estesa per supportare campi numerici, maiuscole / minuscole, tipi di dati arbitrari ecc.

Puoi usarli con incatenarli per priorità di ordinamento:

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

Il punto qui è che il puro JavaScript con approccio funzionale può farti fare molta strada senza librerie esterne o codice complesso. È anche molto efficace, poiché non è necessario eseguire l'analisi delle stringhe


23

Esempio di utilizzo:

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

script:

/**
 * @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;
  };
};

grazie per aver analizzato il problema, sto cercando di capire perché le cifre 1, 0, -1vengono utilizzate per l'ordinamento. Anche con la tua spiegazione sopra, che sembra molto buona-- Non riesco ancora a capirla. Penso sempre -1a quando si usa la proprietà della lunghezza dell'array, ovvero: arr.length = -1significa che l'elemento non è stato trovato. Probabilmente sto mescolando le cose qui, ma potresti aiutarmi a capire perché le cifre 1, 0, -1vengono utilizzate per determinare l'ordine? Grazie.
Chris22,

1
Questo non è del tutto esatto ma potrebbe essere utile pensarlo in questo modo: la funzione passata a array.sort viene chiamata una volta per ogni elemento dell'array, come l'argomento denominato "a". Il valore restituito di ciascuna chiamata di funzione è il modo in cui l'indice (numero posizione corrente) dell'articolo "a" deve essere modificato rispetto all'elemento successivo "b". L'indice determina l'ordine dell'array (0, 1, 2 ecc.) Quindi se "a" è nell'indice 5 e si restituisce -1, quindi 5 + -1 == 4 (spostarlo più avanti) 5 + 0 == 5 (tenerlo dove si trova) ecc. Cammina l'array confrontando 2 vicini ogni volta fino a raggiungere la fine, lasciando un array ordinato.
Jamie Mason,

grazie per aver dedicato del tempo a spiegarlo ulteriormente. Quindi, usando la tua spiegazione e MDN Array.prototype.sort , ti dirò cosa sto pensando a questo: in confronto a ae b, se aè maggiore di baggiungere 1 all'indice di ae posizionarlo dietro b, se aè inferiore a b, sottrarre 1 da ae posizionarlo di fronte b. Se ae bsono uguali, aggiungi 0 a ae lascialo dove si trova.
Chris22,

22

Non ho visto suggerito questo particolare approccio, quindi ecco un metodo di confronto conciso che mi piace usare che funziona per entrambi 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));

Ecco una spiegazione di sortBy():

sortBy()accetta un fnche seleziona quale valore di un oggetto utilizzare come confronto e restituisce una funzione a cui è possibile passare direttamente Array.prototype.sort(). In questo esempio, stiamo usando o.last_nomcome valore per il confronto, quindi ogni volta che riceviamo due oggetti Array.prototype.sort()come

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

e

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

noi usiamo

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

per confrontarli.

Ricordando che fn = o => o.last_nom, possiamo espandere la funzione di confronto all'equivalente

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

L' ||operatore logico OR ha una funzionalità di corto circuito che è molto utile qui. A causa di come funziona, il corpo della funzione sopra significa

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

Come bonus aggiuntivo, ecco l'equivalente in ECMAScript 5 senza funzioni freccia, che purtroppo è più dettagliato:

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

So che questa domanda è troppo vecchia, ma non ho visto alcuna implementazione simile alla mia.
Questa versione è basata sul linguaggio trasformato di 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);
}

Ecco un esempio su come 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'));

14

Ordinamento (altro) matrici complesse di oggetti

Dato che probabilmente incontrerai strutture di dati più complesse come questa matrice, espanderei la soluzione.

TL; DR

Sono versioni più collegabili basate sulla risposta molto bella di @ege-Özcan .

Problema

Ho riscontrato il seguito e non è stato possibile modificarlo. Inoltre non volevo appiattire temporaneamente l'oggetto. Né volevo usare il carattere di sottolineatura / lodash, principalmente per motivi prestazionali e per divertirmi da solo.

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

Obbiettivo

L'obiettivo è quello di ordinarlo principalmente per People.Name.namee secondariamente perPeople.Name.surname

ostacoli

Ora, nella soluzione di base utilizza la notazione parentesi per calcolare le proprietà per ordinarle in modo dinamico. Qui, però, dovremmo costruire anche la notazione parentesi in modo dinamico, dal momento che ci si aspetterebbe che qualcosa del genere People['Name.name']funzioni - il che non funziona.

Semplicemente facendo People['Name']['name'], d'altra parte, è statico e consente solo di andare in fondo alla n livello di -esimo.

Soluzione

L'aggiunta principale qui sarà quella di camminare lungo l'albero degli oggetti e determinare il valore dell'ultima foglia, che è necessario specificare, così come qualsiasi foglia 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;
  }
}

Esempio

Esempio di lavoro su JSBin


2
Perché? Questa non è la risposta alla domanda originale e "l'obiettivo" potrebbe essere risolto semplicemente con People.sort ((a, b) => {return a.Name.name.localeCompare (b.Name.name) || a.Name .surname.localeCompare (b.Name.surname)})
Tero Tolonen,

12

Un'altra opzione:

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

ordina in ordine crescente.


1
La versione leggermente modificata per l'ordinamento per più campi è qui se necessario: stackoverflow.com/questions/6913512/…
Ravshan Samandarov

sembra che potrebbe essere il prossimo: funzione export generateSortFn (prop: string, reverse: boolean = false): (... args: any) => number {return (a, b) => {return a [prop ] <b [prop]? inversione ? 1: -1: a [prop]> b [prop]? inversione ? -1: 1: 0; }; }
Den Kerny

Preferirei un codice più leggibile di quello più breve.
Ravshan Samandarov,

d'accordo, ma in alcuni casi non ho bisogno di guardare le funzioni di utilità.
Den Kerny

11

Una semplice funzione che ordina una matrice di oggetti in base a una proprietà

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

Questa soluzione ha funzionato perfettamente per me nell'ordinamento bidirezionale. Grazie
manjuvreddy il

10

Un modo semplice:

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

Vedi che '.toLowerCase()'è necessario per prevenire errori nel confronto delle stringhe.


4
Puoi usare le funzioni freccia per rendere il codice un po 'più elegante:objs.sort( (a,b) => b.last_nom.toLowerCase() < a.last_nom.toLowerCase() );
Sertage

Questo è sbagliato per lo stesso motivo spiegato qui .
Patrick Roberts,

2
Le funzioni freccia non sono degne di ES5. Tonnellate di motori sono ancora limitate a ES5. Nel mio caso, trovo la risposta sopra significativamente migliore poiché sono su un motore ES5 (forzato dalla mia compagnia)
dylanh724

9

parametri descrittivi aggiuntivi per il codice 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 la soluzione dinamica di Ege con l'idea di Vinay, ottieni una soluzione robusta e piacevole:

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
Grazie per l'idea! A proposito, non incoraggiare le persone a cambiare il prototipo di array (vedere l'avvertenza alla fine del mio esempio).
Ege Özcan,

8

Secondo il tuo esempio, devi ordinare per due campi (cognome, nome), piuttosto che uno. Puoi usare la libreria Alasql per creare questo ordinamento in una riga:

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

Prova questo esempio su jsFiddle .


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

1
In realtà non sembrava funzionare, doveva usare la risposta accettata. Non stava ordinando correttamente.
Madprops,

8

Dato l'esempio originale:

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

Ordina per più campi:

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

Appunti

  • a.localeCompare(b)è universalmente supportato e ritorna -1,0,1 se a<b, a==b, a>brispettivamente.
  • ||nell'ultima riga dà last_nompriorità first_nom.
  • La sottrazione funziona su campi numerici: var age_order = left.age - right.age;
  • Negare l'ordine inverso, return -last_nom_order || -first_nom_order || -age_order;

8

Prova questo,

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

soluzione migliore e facile
Omar Hasan

7

Potrebbe essere necessario convertirli in lettere minuscole per evitare confusione.

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
Ti consigliamo di modificare il tuo post per aggiungere ulteriori spiegazioni su cosa fa il tuo codice e perché risolverà il problema. Una risposta che per lo più contiene solo codice (anche se funziona) di solito non aiuta l'OP a capire il loro problema.
Drenmi,

7

Usando Ramda,

npm installa 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

Questo è un problema semplice, non so perché le persone abbiano una soluzione così complessa.
Una semplice funzione di ordinamento (basata su un algoritmo di ordinamento rapido ):

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

Usa esempio:

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

5
In che modo implementare l'ordinamento rapido in js è una soluzione semplice? Algoritmo semplice ma non una soluzione semplice.
Andrew,

È semplice poiché non utilizza alcuna libreria esterna e non modifica il prototipo dell'oggetto. Secondo me, la lunghezza del codice non ha un impatto diretto sulla complessità del codice
Gil Epshtain,

2
Bene, lasciami provare con parole diverse: come reinventare la ruota è una soluzione semplice?
Roberto14,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.