sort - ordinare array di oggetti javascript




Ordina array di oggetti per valore di proprietà stringa (20)

Ho una matrice 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_nom in JavaScript?

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


Ordinamento (altro) Matrici complesse di oggetti

Dal momento che probabilmente si incontrano strutture dati più complesse come questo array, vorrei espandere la soluzione.

TL; DR

Sono più una versione pluggable basata sulla answer molto bella di @ege-Özcan .

Problema

Ho incontrato il sotto e non ho potuto cambiarlo. Inoltre, non volevo appiattire temporaneamente l'oggetto. Né volevo usare underscore / lodash, principalmente per motivi di prestazioni e il divertimento di implementarlo 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 è di ordinarlo principalmente da People.Name.name e secondariamente da People.Name.surname

ostacoli

Ora, nella soluzione di base utilizza la notazione della parentesi per calcolare le proprietà da ordinare dinamicamente. Qui, però, dovremmo costruire dinamicamente anche la notazione delle parentesi, poiché ci si aspetterebbe che alcune People['Name.name'] come People['Name.name'] funzionerebbero, il che non funziona.

Semplicemente facendo People['Name']['name'] , d'altra parte, è statico e ti consente solo di scendere al n ° livello.

Soluzione

L'aggiunta principale qui sarà quella di camminare lungo l'albero degli oggetti e determinare il valore dell'ultima foglia, devi specificare, così come ogni 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://.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


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

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

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

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

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

La forma più semplice ha solo una funzione inline come questa:

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

Avendo due passaggi con last_nom , first_nom ordinamento sarebbe simile a questo:

// 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 generica potrebbe essere qualcosa del genere:

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

Questa funzione può essere estesa per supportare campi numerici, case sensitive, tipi di dati arbitary, ecc.

Puoi usarli con il concatenarli in base alla 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ò portarvi molto tempo senza librerie esterne o codice complesso. È anche molto efficace, dal momento che non è necessario eseguire l'analisi delle stringhe


Combinando la soluzione dinamica di Ege con l'idea di Vinay, ottieni una soluzione solida e 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");

Ho un pezzo di codice che funziona per me:

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

AGGIORNAMENTO: non funziona sempre, quindi non è corretto :(


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

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

Non capisco perché la gente lo rende così complicato:

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

Per motori più stretti:

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.


Non ho visto questo particolare approccio suggerito, quindi ecco un metodo di confronto terso che mi piace usare che funziona sia per string che per 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) => -(fn(a) < fn(b)) || +(fn(a) > fn(b))
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 fn che seleziona quale valore da un oggetto usare come confronto e restituisce una funzione che può essere passata direttamente ad Array.prototype.sort() . In questo esempio, usiamo o.last_nom come valore per il confronto, quindi ogni volta che riceviamo due oggetti tramite Array.prototype.sort() come

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

e

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

noi usiamo

(a, b) => -(fn(a) < fn(b)) || +(fn(a) > fn(b))

per confrontarli.

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

(a, b) => -(a.last_nom < b.last_nom) || +(a.last_nom > b.last_nom)

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

if (a.last_nom < b.last_nom) return -1
return +(a.last_nom > b.last_nom)

Quindi se a < btorniamo -1, altrimenti se a > bpoi torniamo +1, ma se a == b, allora a < be a > bsono falsi, così ritorna +0.

Come bonus aggiuntivo, ecco l'equivalente in ECMAScript 5.1 senza funzioni di freccia, che sfortunatamente non è altrettanto conciso:

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) {
    return -(fn(a) < fn(b)) || +(fn(a) > fn(b))
  }
}

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

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


Puoi usare

Modo più semplice: Lodash

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

Questo metodo è come _.sortBy tranne per il fatto che consente di specificare gli ordinamenti delle iterate per ordinare. Se gli ordini non sono specificati, tutti i valori sono ordinati in ordine crescente. Altrimenti, specificare un ordine di "desc" per la discesa o "asc" per l'ordinamento crescente dei valori corrispondenti.

argomenti

collection (Array | Object): la raccolta da iterare su. [iteratees = [_. identity]] (Array [] | Function [] | Object [] | string []): I iterate da ordinare. [ordini] (stringa []): gli ordinamenti di iterate.

ritorna

(Matrice): 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']);

Se hai cognomi duplicati potresti 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;
});

So che questa domanda è troppo vecchia, ma non ho visto alcuna implementazione simile alla mia.
Questa versione è basata sull'idioma di trasformazione 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: 'Pako',              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'));

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ù la matrice originale.


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.


ulteriori parametri di desc 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;
    }
}

Lodash.js (superset di Underscore.js )

È opportuno non aggiungere un framework per ogni semplice logica, ma basarsi su framework di utilità ben testati, accelerare lo sviluppo e ridurre la quantità di bug scritti non è un peccato.

Lodash produce codice molto pulito e promuove uno stile di programmazione più funzionale , che si traduce in meno bug. In un lampo diventa chiaro quale sia l'intento se il codice è.

Il problema dell'OP può essere semplicemente risolto come:

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

Ulteriori informazioni? Ad esempio, abbiamo 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 la _.property user.age per specificare il percorso della proprietà che deve essere abbinata. Ordineremo gli oggetti utente per la proprietà age nested. Sì, consente la corrispondenza delle proprietà nidificate!

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

Vuoi invertirlo? Nessun problema. Usa _.reverse .

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

Vuoi combinare entrambi usando Chaining ?

const sortedObjs = _.chain(users).sortBy('user.age').reverse().value();

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

Gli appunti

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

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)

Usando lodash o Underscore, è un pezzo di torta

> const sortedList = _.orderBy(objs, [last_nom], [asc]); // asc or desc

Questo è un problema semplice, non so perché le persone hanno una soluzione così complessa.
Una semplice funzione di ordinamento (basata sull'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');

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




sorting