Come ordinare una matrice di oggetti per più campi?


148

Da questa domanda originale , come dovrei applicare un ordinamento su più campi?

Utilizzando questa struttura leggermente adattata, come classificherei la città (crescente) e quindi il prezzo (decrescente)?

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

Mi è piaciuto il fatto che fosse stata data una risposta che forniva un approccio generale. Laddove prevedo di utilizzare questo codice, dovrò ordinare le date e altre cose. La capacità di "adescare" l'oggetto sembrava utile, se non un po 'ingombrante.

Ho provato a costruire questa risposta in un bell'esempio generico, ma non ho molta fortuna.


Vuoi cercare o ordinare?
Felix Kling,

Qual è esattamente il problema che stai riscontrando utilizzando la seconda risposta che hai collegato?
canone

Non è abbastanza generico. Mi sembra di aggiungere un sacco di codice quando vorrei semplicemente dire che sort(["first-field", "ASC"], ["second-field", "DSC"]); ciò è ulteriormente complicato quando provo ad aggiungere la logica "primer" della prima risposta in modo da poter gestire le date, l'insensibilità ai maiuscoli e così via
Mike

Oppure puoi applicare un peso per ogni campo
onmyway133

Puoi controllare lodash.com/docs/4.17.11#orderBy , se stai bene usando lodash
Deepanshu Arora

Risposte:


83

Un metodo di ordinamento multidimensionale, basato su questa risposta :

Aggiornamento : ecco una versione "ottimizzata". Fa molta più preelaborazione e crea in anticipo una funzione di confronto per ciascuna opzione di ordinamento. Potrebbe aver bisogno di più memoria (in quanto memorizza una funzione per ogni opzione di ordinamento, ma dovrebbe preformarsi un po 'meglio in quanto non è necessario determinare le impostazioni corrette durante il confronto. Tuttavia, non ho eseguito alcuna profilazione.

var sort_by;

(function() {
    // utility functions
    var default_cmp = function(a, b) {
            if (a == b) return 0;
            return a < b ? -1 : 1;
        },
        getCmpFunc = function(primer, reverse) {
            var dfc = default_cmp, // closer in scope
                cmp = default_cmp;
            if (primer) {
                cmp = function(a, b) {
                    return dfc(primer(a), primer(b));
                };
            }
            if (reverse) {
                return function(a, b) {
                    return -1 * cmp(a, b);
                };
            }
            return cmp;
        };

    // actual implementation
    sort_by = function() {
        var fields = [],
            n_fields = arguments.length,
            field, name, reverse, cmp;

        // preprocess sorting options
        for (var i = 0; i < n_fields; i++) {
            field = arguments[i];
            if (typeof field === 'string') {
                name = field;
                cmp = default_cmp;
            }
            else {
                name = field.name;
                cmp = getCmpFunc(field.primer, field.reverse);
            }
            fields.push({
                name: name,
                cmp: cmp
            });
        }

        // final comparison function
        return function(A, B) {
            var a, b, name, result;
            for (var i = 0; i < n_fields; i++) {
                result = 0;
                field = fields[i];
                name = field.name;

                result = field.cmp(A[name], B[name]);
                if (result !== 0) break;
            }
            return result;
        }
    }
}());

Esempio di utilizzo:

homes.sort(sort_by('city', {name:'price', primer: parseInt, reverse: true}));

DEMO


Funzione originale:

var sort_by = function() {
   var fields = [].slice.call(arguments),
       n_fields = fields.length;

   return function(A,B) {
       var a, b, field, key, primer, reverse, result, i;

       for(i = 0; i < n_fields; i++) {
           result = 0;
           field = fields[i];

           key = typeof field === 'string' ? field : field.name;

           a = A[key];
           b = B[key];

           if (typeof field.primer  !== 'undefined'){
               a = field.primer(a);
               b = field.primer(b);
           }

           reverse = (field.reverse) ? -1 : 1;

           if (a<b) result = reverse * -1;
           if (a>b) result = reverse * 1;
           if(result !== 0) break;
       }
       return result;
   }
};

DEMO


2
Per la cronaca, questa funzione potrebbe essere migliorata preelaborando l'elenco degli argomenti e creando un "array di opzioni di ordinamento" uniforme. Questo è lasciato come esercizio per il lettore;)
Felix Kling

@Mike: Ok ... finalmente;) Ora vedi che è più complesso, poiché le opzioni sono preelaborate, ma la funzione di confronto finale (vedi commento) è molto più semplice e (si spera) porta a prestazioni migliori. Più opzioni di ordinamento hai, maggiore sarà il vantaggio di questo metodo.
Felix Kling,

166

per una soluzione non generica e semplice al tuo problema esatto:

homes.sort(
   function(a, b) {          
      if (a.city === b.city) {
         // Price is only important when cities are the same
         return b.price - a.price;
      }
      return a.city > b.city ? 1 : -1;
   });

6
Penso che questa demo sia ciò che vuole l'OP => jsfiddle.net/zJ6UA/533
Amin Jafari

3
Questa è l'idea giusta, ma la logica è tutta sbagliata. Non è possibile sottrarre una stringa non numerica da un'altra stringa e l' ifistruzione non ha senso.
JLRè il

6
Puoi usare a.localeCompare(b)nell'ultima riga per il confronto delle stringhe ... vedi i documenti
Michael P

2
Il primo confronto tra città non dovrebbe controllare l'uguaglianza, non la disuguaglianza? In altre parole, la linea non dovrebbe essere if (a.city === b.city)? Cioè, se le due città sono uguali, confronta i prezzi, altrimenti confronta le città.
Steven Rands,

2
una delle migliori risposte. tnx.
Jonathana,

56

È possibile utilizzare un approccio di ordinamento concatenato prendendo il delta di valori fino a quando non raggiunge un valore diverso da zero.

var data = [{ 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" }];

data.sort(function (a, b) {
    return a.city.localeCompare(b.city) || b.price - a.price;
});

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Oppure, usando es6, semplicemente:

data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);

17
Mi sto perdendo qualcosa? Perché usare 60 righe di codice per qualcosa che può essere fatto in 1. Semplice, chiaro, conciso. Dovrebbe essere la risposta accettata IMO.
Erez Cohen,

uno dei grandi problemi ora di SO è che le vecchie risposte - spesso ben sostituite da soluzioni migliori che utilizzano nuove funzionalità linguistiche (ad esempio ES5-6-7) mantengono i loro vecchi punteggi e tutti dobbiamo scorrere verso il basso per trovare il "vero" migliore soluzioni! SO dovrebbe scadere i voti nel tempo per affrontare questo problema, perché il problema peggiora sempre con il passare del tempo.
Andy Lorenz,

53

Ecco un semplice approccio funzionale. Specificare l'ordinamento utilizzando l'array. Prepara meno per specificare l'ordine decrescente.

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

homes.sort(fieldSorter(['city', '-price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
    return function (a, b) {
        return fields
            .map(function (o) {
                var dir = 1;
                if (o[0] === '-') {
                   dir = -1;
                   o=o.substring(1);
                }
                if (a[o] > b[o]) return dir;
                if (a[o] < b[o]) return -(dir);
                return 0;
            })
            .reduce(function firstNonZeroValue (p,n) {
                return p ? p : n;
            }, 0);
    };
}

Modifica: in ES6 è ancora più breve!

"use strict";
const fieldSorter = (fields) => (a, b) => fields.map(o => {
    let dir = 1;
    if (o[0] === '-') { dir = -1; o=o.substring(1); }
    return a[o] > b[o] ? dir : a[o] < b[o] ? -(dir) : 0;
}).reduce((p, n) => p ? p : n, 0);

const 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}];
const sortedHomes = homes.sort(fieldSorter(['state', '-price']));

document.write('<pre>' + JSON.stringify(sortedHomes, null, '\t') + '</pre>')


6
Ho trovato questa funzione piuttosto ordinata, quindi ho apportato un piccolo miglioramento delle prestazioni fino al 90% a seconda del parser. Ho creato una suite di test e di sintesi .
php_nub_qq

Sulla base dei dati di esempio sembra che i numeri sono allineati come previsto, ma quando ho provato attuare questi numeri in cui l'ordinamento più come stringhe ... [10,100,11,9]. Ho dimenticato qualcosa?
Mark Carpenter Jr,

@MarkCarpenterJr. Non sono sicuro cosa intendi. il mio esempio ordina i tipi numerici correttamente. Puoi condividere la tua implementazione come una domanda e fare riferimento a me nei commenti in modo che io la veda? Quindi posso controllare.
chriskelly,

@MarkCarpenterJr. L'ho appena visto. Ho aggiunto una spiegazione nei commenti.
chriskelly,

32

Oggi ho creato una selezionatrice multi generica piuttosto generica. Puoi dare un'occhiata a thenBy.js qui: https://github.com/Teun/thenBy.js

Ti permette di usare lo standard Array.sort, ma con lo stile firstBy (). ThenBy (). ThenBy (). È molto meno codice e complessità rispetto alle soluzioni sopra pubblicate.


8
Bene, quando chiami 3 volte, non è garantito che la 2a chiamata lasci inalterato l'ordine del primo per gli articoli in cui la seconda chiamata non fa differenza.
Teun D

13

La seguente funzione ti permetterà di ordinare una matrice di oggetti su una o più proprietà, sia ascendenti (predefinite) o decrescenti su ciascuna proprietà, e ti permetterà di scegliere se eseguire o meno confronti tra maiuscole e minuscole. Per impostazione predefinita, questa funzione esegue ordinamenti senza distinzione tra maiuscole e minuscole.

Il primo argomento deve essere l'array che contiene gli oggetti. Gli argomenti successivi devono essere un elenco separato da virgole di stringhe che fanno riferimento alle diverse proprietà dell'oggetto da ordinare. L'ultimo argomento (che è facoltativo) è un valore booleano per scegliere se eseguire o meno ordinamenti con distinzione tra maiuscole e minuscole - utilizzare trueper ordinamenti con distinzione tra maiuscole e minuscole.

La funzione ordinerà ogni proprietà / chiave in ordine crescente per impostazione predefinita. Se si desidera una particolare chiave di ordinamento in ordine decrescente, poi invece passare in una matrice in questo formato: ['property_name', true].

Ecco alcuni esempi di utilizzo della funzione seguiti da una spiegazione (dove si homestrova un array contenente gli oggetti):

objSort(homes, 'city') -> ordina per città (crescente, sensibile al maiuscolo / minuscolo)

objSort(homes, ['city', true]) -> ordina per città (decrescente, sensibile al maiuscolo / minuscolo)

objSort(homes, 'city', true)-> ordina per città quindi prezzo (crescente, maiuscolo / minuscolo )

objSort(homes, 'city', 'price') -> ordina per città quindi prezzo (entrambi in ordine crescente, con distinzione tra maiuscole e minuscole)

objSort(homes, 'city', ['price', true]) -> ordina per città (crescente) quindi prezzo (decrescente), sensibile al maiuscolo / minuscolo)

E senza ulteriori indugi, ecco la funzione:

function objSort() {
    var args = arguments,
        array = args[0],
        case_sensitive, keys_length, key, desc, a, b, i;

    if (typeof arguments[arguments.length - 1] === 'boolean') {
        case_sensitive = arguments[arguments.length - 1];
        keys_length = arguments.length - 1;
    } else {
        case_sensitive = false;
        keys_length = arguments.length;
    }

    return array.sort(function (obj1, obj2) {
        for (i = 1; i < keys_length; i++) {
            key = args[i];
            if (typeof key !== 'string') {
                desc = key[1];
                key = key[0];
                a = obj1[args[i][0]];
                b = obj2[args[i][0]];
            } else {
                desc = false;
                a = obj1[args[i]];
                b = obj2[args[i]];
            }

            if (case_sensitive === false && typeof a === 'string') {
                a = a.toLowerCase();
                b = b.toLowerCase();
            }

            if (! desc) {
                if (a < b) return -1;
                if (a > b) return 1;
            } else {
                if (a > b) return -1;
                if (a < b) return 1;
            }
        }
        return 0;
    });
} //end of objSort() function

Ed ecco alcuni dati di esempio:

var homes = [{
    "h_id": "3",
    "city": "Dallas",
    "state": "TX",
    "zip": "75201",
    "price": 162500
}, {
    "h_id": "4",
    "city": "Bevery Hills",
    "state": "CA",
    "zip": "90210",
    "price": 1000000
}, {
    "h_id": "5",
    "city": "new york",
    "state": "NY",
    "zip": "00010",
    "price": 1000000
}, {
    "h_id": "6",
    "city": "Dallas",
    "state": "TX",
    "zip": "85000",
    "price": 300000
}, {
    "h_id": "7",
    "city": "New York",
    "state": "NY",
    "zip": "00020",
    "price": 345000
}];

8

Questo è un trucco completo, ma penso che aggiunga valore a questa domanda perché è fondamentalmente una funzione di libreria predefinita che è possibile utilizzare immediatamente.

Se il tuo codice ha accesso a lodashuna libreria compatibile con lodash come underscoreallora puoi usare il _.sortBymetodo. Lo snippet di seguito viene copiato direttamente dalla documentazione lodash .

I risultati commentati negli esempi sembrano restituire matrici di matrici ma ciò mostra solo l'ordine e non i risultati effettivi che sono una matrice di oggetti.

var users = [
  { 'user': 'fred',   'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];

_.sortBy(users, [function(o) { return o.user; }]);
 // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]

7

Eccone un altro forse più vicino alla tua idea di sintassi

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {}; // primers are optional

    properties = properties.map(function(prop) {
        if( !(prop instanceof Array) ) {
            prop = [prop, 'asc']
        }
        if( prop[1].toLowerCase() == 'desc' ) {
            prop[1] = -1;
        } else {
            prop[1] = 1;
        }
        return prop;
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

// just for fun use this to reverse the city name when sorting
function demoPrimer(str) {
    return str.split('').reverse().join('');
}

// Example
sortObjects(homes, ['city', ['price', 'desc']], {city: demoPrimer});

Demo: http://jsfiddle.net/Nq4dk/2/


Modifica: solo per divertimento, ecco una variante che richiede solo una stringa di tipo sql, quindi puoi farlosortObjects(homes, "city, price desc")

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {};

    properties = properties.split(/\s*,\s*/).map(function(prop) {
        prop = prop.match(/^([^\s]+)(\s*desc)?/i);
        if( prop[2] && prop[2].toLowerCase() === 'desc' ) {
            return [prop[1] , -1];
        } else {
            return [prop[1] , 1];
        }
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

questa soluzione è pulita ma non performante a causa del confronto di array. puoi semplicemente dare un'occhiata alle proprietà per tenere traccia del valore confrontato e non è zero, return. è molto più veloce.
amankapur91,

4

Uno più semplice:

var someArray = [...];

function generateSortFn(props) {
    return function (a, b) {
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            var name = prop.name;
            var reverse = prop.reverse;
            if (a[name] < b[name])
                return reverse ? 1 : -1;
            if (a[name] > b[name])
                return reverse ? -1 : 1;
        }
        return 0;
    };
};

someArray.sort(generateSortFn([{name: 'prop1', reverse: true}, {name: 'prop2'}]));

3

Mi piace l'approccio di SnowBurnt ma ha bisogno di una modifica per testare l'equivalenza sulla città NON una differenza.

homes.sort(
   function(a,b){
      if (a.city==b.city){
         return (b.price-a.price);
      } else {
         return (a.city-b.city);
      }
   });

3

Ecco un ordinamento multidimensionale generico, che consente di invertire e / o mappare su ogni livello.

Scritto in dattiloscritto. Per Javascript, dai un'occhiata a questo JSFiddle

Il codice

type itemMap = (n: any) => any;

interface SortConfig<T> {
  key: keyof T;
  reverse?: boolean;
  map?: itemMap;
}

export function byObjectValues<T extends object>(keys: ((keyof T) | SortConfig<T>)[]): (a: T, b: T) => 0 | 1 | -1 {
  return function(a: T, b: T) {
    const firstKey: keyof T | SortConfig<T> = keys[0];
    const isSimple = typeof firstKey === 'string';
    const key: keyof T = isSimple ? (firstKey as keyof T) : (firstKey as SortConfig<T>).key;
    const reverse: boolean = isSimple ? false : !!(firstKey as SortConfig<T>).reverse;
    const map: itemMap | null = isSimple ? null : (firstKey as SortConfig<T>).map || null;

    const valA = map ? map(a[key]) : a[key];
    const valB = map ? map(b[key]) : b[key];
    if (valA === valB) {
      if (keys.length === 1) {
        return 0;
      }
      return byObjectValues<T>(keys.slice(1))(a, b);
    }
    if (reverse) {
      return valA > valB ? -1 : 1;
    }
    return valA > valB ? 1 : -1;
  };
}

Esempi di utilizzo

Ordinamento di un array di persone in base al cognome, quindi al nome:

interface Person {
  firstName: string;
  lastName: string;
}

people.sort(byObjectValues<Person>(['lastName','firstName']));

Ordina i codici lingua in base al nome , non al codice lingua (vedi map), quindi in base alla versione decrescente (vedi reverse).

interface Language {
  code: string;
  version: number;
}

// languageCodeToName(code) is defined elsewhere in code

languageCodes.sort(byObjectValues<Language>([
  {
    key: 'code',
    map(code:string) => languageCodeToName(code),
  },
  {
    key: 'version',
    reverse: true,
  }
]));

2

Un modo dinamico per farlo con i tasti MULTIPLI:

  • filtra valori univoci da ciascuna colonna / chiave di ordinamento
  • mettere in ordine o invertirlo
  • aggiungere pesi zeropad di larghezza per ogni oggetto in base ai valori delle chiavi indexOf (value)
  • ordina usando pesi calcolati

inserisci qui la descrizione dell'immagine

Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) { 
    sorts.map(sort => {            
        sort.uniques = Array.from(
            new Set(this.map(obj => obj[sort.key]))
        );

        sort.uniques = sort.uniques.sort((a, b) => {
            if (typeof a == 'string') {
                return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
            }
            else if (typeof a == 'number') {
                return sort.inverse ? (a < b) : (a > b ? 1 : 0);
            }
            else if (typeof a == 'boolean') {
                let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
                return x;
            }
            return 0;
        });
    });

    const weightOfObject = (obj) => {
        let weight = "";
        sorts.map(sort => {
            let zeropad = `${sort.uniques.length}`.length;
            weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
        });
        //obj.weight = weight; // if you need to see weights
        return weight;
    }

    this.sort((a, b) => {
        return weightOfObject(a).localeCompare( weightOfObject(b) );
    });

    return this;
}
});

Uso:

// works with string, number and boolean
let sortered = your_array.orderBy([
    {key: "type", inverse: false}, 
    {key: "title", inverse: false},
    {key: "spot", inverse: false},
    {key: "internal", inverse: true}
]);

inserisci qui la descrizione dell'immagine


1

Ecco una versione generica della soluzione di @ Snowburnt:

var sortarray = [{field:'city', direction:'asc'}, {field:'price', direction:'desc'}];
array.sort(function(a,b){
    for(var i=0; i<sortarray.length; i++){
        retval = a[sortarray[i].field] < b[sortarray[i].field] ? -1 : a[sortarray[i].field] > b[sortarray[i].field] ? 1 : 0;
        if (sortarray[i].direction == "desc") {
            retval = retval * -1;
        }
        if (retval !== 0) {
            return retval;
        }
    }
}


})

Questo si basa su una sorta di routine che sto usando. Non ho testato questo codice specifico quindi potrebbe avere degli errori ma hai avuto l'idea. L'idea è di ordinare in base al primo campo che indica una differenza e quindi fermarsi e passare al record successivo. Quindi, se stai ordinando per tre campi e il primo campo nel confronto è sufficiente per determinare l'ordinamento dei due record da ordinare, allora restituisci quel risultato di ordinamento e vai al record successivo.

L'ho provato (in realtà con una logica di ordinamento un po 'più complessa) su 5000 dischi e l'ho fatto in un batter d'occhio. Se in realtà stai caricando più di 1000 record sul client, probabilmente dovresti utilizzare l'ordinamento e il filtro sul lato server.

Questo codice non gestisce la distinzione tra maiuscole e minuscole, ma lascio al lettore la gestione di questa banale modifica.


1

Ecco la mia soluzione basata sul linguaggio trasformato Schwartziano , spero che lo trovi utile.

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

1
Ho provato alcune cose su questo (e altre pagine). Questa soluzione di a8m è stata l'unica a funzionare per la mia situazione: gist.github.com/cemerson/f1f1434286c1262b403f3d85c96688e0
Christopher D. Emerson,

1

Un altro modo

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"}
    ];
function sortBy(ar) {
  return ar.sort((a, b) => a.city === b.city ?
      b.price.toString().localeCompare(a.price) :
      a.city.toString().localeCompare(b.city));
}
console.log(sortBy(homes));


0
function sortMultiFields(prop){
    return function(a,b){
        for(i=0;i<prop.length;i++)
        {
            var reg = /^\d+$/;
            var x=1;
            var field1=prop[i];
            if(prop[i].indexOf("-")==0)
            {
                field1=prop[i].substr(1,prop[i].length);
                x=-x;
            }

            if(reg.test(a[field1]))
            {
                a[field1]=parseFloat(a[field1]);
                b[field1]=parseFloat(b[field1]);
            }
            if( a[field1] > b[field1])
                return x;
            else if(a[field1] < b[field1])
                return -x;
        }
    }
}

Come utilizzare il segno (put - (meno) prima del campo se si desidera ordinare in un campo particolare in ordine decrescente)

homes.sort(sortMultiFields(["city","-price"]));

Utilizzando la funzione sopra è possibile ordinare qualsiasi array JSON con più campi. Non è necessario cambiare il corpo della funzione


0

Adattamento della risposta di @chriskelly.


La maggior parte delle risposte trascura il fatto che il prezzo non verrà ordinato correttamente se il valore è tra le diecimila e inferiore o superiore a un milione. La resaon essendo JS ordina in ordine alfabetico. È stato risposto abbastanza bene qui, perché JavaScript non può ordinare "5, 10, 1" e qui Come ordinare correttamente un array di numeri interi .

Alla fine dobbiamo fare una valutazione se il campo o il nodo che stiamo ordinando è un numero. Non sto dicendo che l'utilizzo parseInt()in questo caso sia la risposta corretta, i risultati ordinati sono più importanti.

var homes = [{
  "h_id": "2",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62500"
}, {
  "h_id": "1",
  "city": "Dallas",
  "state": "TX",
  "zip": "75201",
  "price": "62510"
}, {
  "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"
}];

homes.sort(fieldSorter(['price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
  return function(a, b) {
    return fields
      .map(function(o) {
        var dir = 1;
        if (o[0] === '-') {
          dir = -1;
          o = o.substring(1);
        }
        if (!parseInt(a[o]) && !parseInt(b[o])) {
          if (a[o] > b[o]) return dir;
          if (a[o] < b[o]) return -(dir);
          return 0;
        } else {
          return dir > 0 ? a[o] - b[o] : b[o] - a[o];
        }
      })
      .reduce(function firstNonZeroValue(p, n) {
        return p ? p : n;
      }, 0);
  };
}
document.getElementById("output").innerHTML = '<pre>' + JSON.stringify(homes, null, '\t') + '</pre>';
<div id="output">

</div>


Un violino con cui provare


Il problema è con i dati che si sta tentando di ordinare. pricenell'esempio è in formato stringa. Se vuoi che funzioni correttamente con il mio esempio usa map per convertire il campo che vuoi prima numerare. vale a direconst correctedHomes = homes.map(h => ({...h, price: +h.price}))
chriskelly,

0

Wow, ci sono alcune soluzioni complesse qui. Così complesso ho deciso di inventare qualcosa di più semplice ma anche abbastanza potente. Ecco qui;

function sortByPriority(data, priorities) {
  if (priorities.length == 0) {
    return data;
  }

  const nextPriority = priorities[0];
  const remainingPriorities = priorities.slice(1);

  const matched = data.filter(item => item.hasOwnProperty(nextPriority));
  const remainingData = data.filter(item => !item.hasOwnProperty(nextPriority));

  return sortByPriority(matched, remainingPriorities)
    .sort((a, b) => (a[nextPriority] > b[nextPriority]) ? 1 : -1)
    .concat(sortByPriority(remainingData, remainingPriorities));
}

Ed ecco un esempio di come lo usi.

const data = [
  { id: 1,                         mediumPriority: 'bbb', lowestPriority: 'ggg' },
  { id: 2, highestPriority: 'bbb', mediumPriority: 'ccc', lowestPriority: 'ggg' },
  { id: 3,                         mediumPriority: 'aaa', lowestPriority: 'ggg' },
];

const priorities = [
  'highestPriority',
  'mediumPriority',
  'lowestPriority'
];


const sorted = sortByPriority(data, priorities);

Questo ordinerà prima in base alla precedenza degli attributi, quindi in base al valore degli attributi.


0

Ecco un modo estensibile per ordinare per più campi.

homes.sort(function(left, right) {
    var city_order = left.city.localeCompare(right.city);
    var price_order = parseInt(left.price) - parseInt(right.price);
    return city_order || -price_order;
});

Appunti

  • a.localeCompare(b)è universalmente supportato e ritorna -1,0,1 se a<b, a==b, a>brispettivamente.
  • La sottrazione funziona su campi numerici.
  • ||nell'ultima riga dà citypriorità price.
  • Negare l'ordine inverso in qualsiasi campo, come in -price_order
  • Confronto Data , var date_order = new Date(left.date) - new Date(right.date);funziona come numerici, perché data la matematica si trasforma in millisecondi dal 1970.
  • Aggiungi campi nella catena or return city_order || -price_order || date_order;

0

Penso che questo potrebbe essere il modo più semplice per farlo.

https://coderwall.com/p/ebqhca/javascript-sort-by-two-fields

È davvero semplice e l'ho provato con 3 diverse coppie di valori-chiave e ha funzionato alla grande.

Ecco un semplice esempio, guarda il link per maggiori dettagli

testSort(data) {
    return data.sort(
        a['nameOne'] > b['nameOne'] ? 1
        : b['nameOne'] > a['nameOne'] ? -1 : 0 ||
        a['date'] > b['date'] ||
        a['number'] - b['number']
    );
}

0

Ecco il mio per riferimento, con esempio:

function msort(arr, ...compFns) {
  let fn = compFns[0];
  arr = [].concat(arr);
  let arr1 = [];
  while (arr.length > 0) {
    let arr2 = arr.splice(0, 1);
    for (let i = arr.length; i > 0;) {
      if (fn(arr2[0], arr[--i]) === 0) {
        arr2 = arr2.concat(arr.splice(i, 1));
      }
    }
    arr1.push(arr2);
  }

  arr1.sort(function (a, b) {
    return fn(a[0], b[0]);
  });

  compFns = compFns.slice(1);
  let res = [];
  arr1.map(a1 => {
    if (compFns.length > 0) a1 = msort(a1, ...compFns);
    a1.map(a2 => res.push(a2));
  });
  return res;
}

let tstArr = [{ id: 1, sex: 'o' }, { id: 2, sex: 'm' }, { id: 3, sex: 'm' }, { id: 4, sex: 'f' }, { id: 5, sex: 'm' }, { id: 6, sex: 'o' }, { id: 7, sex: 'f' }];

function tstFn1(a, b) {
  if (a.sex > b.sex) return 1;
  else if (a.sex < b.sex) return -1;
  return 0;
}

function tstFn2(a, b) {
  if (a.id > b.id) return -1;
  else if (a.id < b.id) return 1;
  return 0;
}

console.log(JSON.stringify(msort(tstArr, tstFn1, tstFn2)));
//output:
//[{"id":7,"sex":"f"},{"id":4,"sex":"f"},{"id":5,"sex":"m"},{"id":3,"sex":"m"},{"id":2,"sex":"m"},{"id":6,"sex":"o"},{"id":1,"sex":"o"}]

0

Stavo cercando qualcosa di simile e ho finito con questo:

Innanzitutto abbiamo una o più funzioni di ordinamento, che restituiscono sempre 0, 1 o -1:

const sortByTitle = (a, b): number => 
  a.title === b.title ? 0 : a.title > b.title ? 1 : -1;

È possibile creare più funzioni per ogni altra proprietà su cui si desidera ordinare.

Quindi ho una funzione che combina queste funzioni di ordinamento in una:

const createSorter = (...sorters) => (a, b) =>
  sorters.reduce(
    (d, fn) => (d === 0 ? fn(a, b) : d),
    0
  );

Questo può essere usato per combinare le funzioni di ordinamento sopra in modo leggibile:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

Quando una funzione di ordinamento restituisce 0, la successiva funzione di ordinamento verrà chiamata per un ulteriore ordinamento.


0

Solo un'altra opzione. Considerare di utilizzare la seguente funzione di utilità:

/** Performs comparing of two items by specified properties
 * @param  {Array} props for sorting ['name'], ['value', 'city'], ['-date']
 * to set descending order on object property just add '-' at the begining of property
 */
export const compareBy = (...props) => (a, b) => {
  for (let i = 0; i < props.length; i++) {
    const ascValue = props[i].startsWith('-') ? -1 : 1;
    const prop = props[i].startsWith('-') ? props[i].substr(1) : props[i];
    if (a[prop] !== b[prop]) {
      return a[prop] > b[prop] ? ascValue : -ascValue;
    }
  }
  return 0;
};

Esempio di utilizzo (nel tuo caso):

homes.sort(compareBy('city', '-price'));

Va notato che questa funzione può essere ancora più generalizzata per poter utilizzare proprietà nidificate come 'address.city' o 'style.size.width' ecc.


0

Questo è un algoritmo ricorsivo per ordinare in base a più campi e avere la possibilità di formattare i valori prima del confronto.

var data = [
{
    "id": 1,
    "ship": null,
    "product": "Orange",
    "quantity": 7,
    "price": 92.08,
    "discount": 0
},
{
    "id": 2,
    "ship": "2017-06-14T23:00:00.000Z".toDate(),
    "product": "Apple",
    "quantity": 22,
    "price": 184.16,
    "discount": 0
},
...
]
var sorts = ["product", "quantity", "ship"]

// comp_val formats values and protects against comparing nulls/undefines
// type() just returns the variable constructor
// String.lower just converts the string to lowercase.
// String.toDate custom fn to convert strings to Date
function comp_val(value){
    if (value==null || value==undefined) return null
    var cls = type(value)
    switch (cls){
        case String:
            return value.lower()
    }
    return value
}

function compare(a, b, i){
    i = i || 0
    var prop = sorts[i]
    var va = comp_val(a[prop])
    var vb = comp_val(b[prop])

    // handle what to do when both or any values are null
    if (va == null || vb == null) return true

    if ((i < sorts.length-1) && (va == vb)) {
        return compare(a, b, i+1)
    } 
    return va > vb
}

var d = data.sort(compare);
console.log(d);

Se aeb sono uguali, proverà semplicemente il campo successivo fino a quando non sarà disponibile nessuno.


-1
homes.sort(function(a,b) { return a.city - b.city } );
homes.sort(function(a,b){
    if (a.city==b.city){
        return parseFloat(b.price) - parseFloat(a.price);
    } else {
        return 0;
    }
});

Perché non mettere tutto in un'unica funzione? Se la città non è uguale, restituisci il differenziale, altrimenti differisci il prezzo.
Fisico pazzo,

-1

Qui "AffiliateDueDate" e "Title" sono colonne, entrambe ordinate in ordine crescente.

array.sort(function(a, b) {

               if (a.AffiliateDueDate > b.AffiliateDueDate ) return 1;
               else if (a.AffiliateDueDate < b.AffiliateDueDate ) return -1;
               else if (a.Title > b.Title ) return 1;
               else if (a.Title < b.Title ) return -1;
               else return 0;
             })

-1

Ordinamento su due campi data e un esempio di campo numerico:

var generic_date =  new Date(2070, 1, 1);
checkDate = function(date) {
  return Date.parse(date) ? new Date(date): generic_date;
}

function sortData() {  
  data.sort(function(a,b){
    var deltaEnd = checkDate(b.end) - checkDate(a.end);
    if(deltaEnd) return deltaEnd;

    var deltaRank = a.rank - b.rank;
    if (deltaRank) return deltaRank;

    var deltaStart = checkDate(b.start) - checkDate(a.start);
    if(deltaStart) return deltaStart;

    return 0;
  });
}

http://jsfiddle.net/hcWgf/57/


-1
function sort(data, orderBy) {
        orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
        return data.sort((a, b) => {
            for (let i = 0, size = orderBy.length; i < size; i++) {
                const key = Object.keys(orderBy[i])[0],
                    o = orderBy[i][key],
                    valueA = a[key],
                    valueB = b[key];
                if (!(valueA || valueB)) {
                    console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
                    return [];
                }
                if (+valueA === +valueA) {
                    return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
                } else {
                    if (valueA.localeCompare(valueB) > 0) {
                        return o.toLowerCase() === 'desc' ? -1 : 1;
                    } else if (valueA.localeCompare(valueB) < 0) {
                        return o.toLowerCase() === 'desc' ? 1 : -1;
                    }
                }
            }
        });
    }

Utilizzando:

sort(homes, [{city : 'asc'}, {price: 'desc'}])


-1

Che ne dici di questa semplice soluzione:

const sortCompareByCityPrice = (a, b) => {
    let comparison = 0
    // sort by first criteria
    if (a.city > b.city) {
        comparison = 1
    }
    else if (a.city < b.city) {
        comparison = -1
    }
    // If still 0 then sort by second criteria descending
    if (comparison === 0) {
        if (parseInt(a.price) > parseInt(b.price)) {
            comparison = -1
        }
        else if (parseInt(a.price) < parseInt(b.price)) {
            comparison = 1
        }
    }
    return comparison 
}

Sulla base di questa domanda array di ordinamento JavaScript per più campi (numerici)

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.