Wie sortiere ich ein Array von Objekten nach mehreren Feldern?


147

Wie würde ich nach dieser ursprünglichen Frage eine Sortierung auf mehrere Felder anwenden?

Wie würde ich mit dieser leicht angepassten Struktur die Stadt (aufsteigend) und dann den Preis (absteigend) sortieren?

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

Ich mochte die Tatsache, dass eine Antwort gegeben wurde, die einen allgemeinen Ansatz lieferte. Wo ich diesen Code verwenden möchte, muss ich Datumsangaben und andere Dinge sortieren. Die Fähigkeit, das Objekt zu "grundieren", schien praktisch, wenn nicht sogar etwas umständlich.

Ich habe versucht, diese Antwort in ein schönes allgemeines Beispiel zu integrieren, aber ich habe nicht viel Glück.


Möchten Sie suchen oder sortieren?
Felix Kling

Was genau ist das Problem bei der Verwendung der zweiten Antwort, die Sie verlinkt haben?
Kanon

Es ist nicht generisch genug. Ich scheine ein Meer von Code hinzuzufügen, wenn ich einfach sagen möchte. sort(["first-field", "ASC"], ["second-field", "DSC"]); Dies ist noch komplizierter, wenn ich versuche, die "Primer" -Logik der ersten Antwort hinzuzufügen, damit ich mit Datumsangaben, Groß- und Kleinschreibung usw. umgehen kann
Mike

Oder Sie können jedem Feld ein Gewicht
zuweisen

Sie können lodash.com/docs/4.17.11#orderBy überprüfen , ob Sie mit lodash
Deepanshu Arora

Antworten:


83

Eine mehrdimensionale Sortiermethode, basierend auf dieser Antwort :

Update : Hier ist eine "optimierte" Version. Es führt viel mehr Vorverarbeitung durch und erstellt zuvor eine Vergleichsfunktion für jede Sortieroption. Es benötigt möglicherweise mehr Speicher (da es eine Funktion für jede Sortieroption speichert, sollte sich jedoch etwas besser entwickeln, da während des Vergleichs nicht die richtigen Einstellungen ermittelt werden müssen. Ich habe jedoch keine Profilerstellung durchgeführt.

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

Anwendungsbeispiel:

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

DEMO


Ursprüngliche Funktion:

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
Für den Datensatz könnte diese Funktion noch verbessert werden, indem die Argumentliste vorverarbeitet und ein einheitliches "Sortieroptionen-Array" erstellt wird. Dies bleibt als Übung für den Leser;)
Felix Kling

@Mike: Ok ... endlich;) Sie sehen, dass es jetzt komplexer ist, da die Optionen vorverarbeitet sind, aber die endgültige Vergleichsfunktion (siehe Kommentar) ist viel einfacher, was (hoffentlich) zu einer besseren Leistung führt. Je mehr Sortieroptionen Sie haben, desto mehr Vorteile haben Sie von dieser Methode.
Felix Kling

165

für eine nicht generische, einfache Lösung für Ihr genaues Problem:

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
Ich denke, diese Demo ist das, was das OP will => jsfiddle.net/zJ6UA/533
Amin Jafari

3
Das hat die richtige Idee, aber die Logik ist alles falsch. Sie können eine nicht numerische Zeichenfolge nicht von einer anderen Zeichenfolge subtrahieren, und die ifAnweisung macht keinen Sinn.
JLRishe

6
Sie können a.localeCompare(b)in der letzten Zeile für den Zeichenfolgenvergleich verwenden ... siehe die Dokumente
Michael P

2
Sollte der erste Stadtvergleich nicht die Gleichheit prüfen, nicht die Ungleichheit? Mit anderen Worten, sollte die Linie nicht sein if (a.city === b.city)? Das heißt, wenn die beiden Städte gleich sind, vergleichen Sie die Preise, andernfalls vergleichen Sie die Städte.
Steven Rands

2
eine der besten Antworten. tnx.
Jonathana

54

Sie können einen verketteten Sortieransatz verwenden, indem Sie das Delta der Werte verwenden, bis es einen Wert ungleich Null erreicht.

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

Oder mit es6 einfach:

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

16
Vermisse ich etwas Warum 60 Codezeilen für etwas verwenden, das in 1 erledigt werden kann? Einfach, klar, präzise. Sollte die akzeptierte Antwort IMO sein.
Erez Cohen

Eines der großen Probleme von SO ist jetzt, dass alte Antworten - oftmals ersetzt durch bessere Lösungen mit neuen Sprachfunktionen (z. B. ES5-6-7) - ihre alten Ergebnisse beibehalten und wir alle nach unten scrollen müssen, um die "echten" Besten zu finden Lösungen! SO sollte die Abstimmung im Laufe der Zeit ablaufen, um dies zu beheben, da das Problem mit der Zeit immer schlimmer wird.
Andy Lorenz

53

Hier ist ein einfacher funktionaler Ansatz. Geben Sie die Sortierreihenfolge mithilfe des Arrays an. Stellen Sie ein Minus voran , um die absteigende Reihenfolge anzugeben.

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

Edit: in ES6 ist es noch kürzer!

"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
Ich fand diese Funktion ziemlich ordentlich, so dass ich je nach Parser eine kleine Leistungsverbesserung von bis zu 90% vorgenommen habe. Ich habe eine Kern- und Testsuite erstellt .
php_nub_qq

Basierend auf den Beispieldaten wie es Zahlen sieht aus wie erwartet sortiert, aber als ich versuchte , diese Zahlen Umsetzung in denen mehr wie Strings Sortierung ... [10,100,11,9]. Habe ich etwas verpasst?
Mark Carpenter Jr.

@ MarkCarpenterJr. Nicht sicher was du meinst. In meinem Beispiel werden numerische Typen korrekt sortiert. Können Sie Ihre Implementierung als Frage teilen und mich in den Kommentaren referenzieren, damit ich sie sehe? Dann kann ich nachsehen.
Chriskelly

@ MarkCarpenterJr. Ich habe es gerade entdeckt. Ich habe in den Kommentaren eine Erklärung hinzugefügt.
Chriskelly

32

Ich habe heute einen ziemlich generischen Multifunktionssortierer erstellt. Sie können sich thenBy.js hier ansehen: https://github.com/Teun/thenBy.js

Sie können die Standard-Array.sort verwenden, jedoch mit dem Stil firstBy (). ThenBy (). ThenBy (). Es ist viel weniger Code und Komplexität als die oben beschriebenen Lösungen.


8
Wenn Sie dreimal anrufen, bleibt beim zweiten Anruf nicht garantiert, dass die Reihenfolge des ersten Anrufs für Artikel unberührt bleibt, bei denen der zweite Anruf keinen Unterschied macht.
Teun D

13

Mit der folgenden Funktion können Sie ein Array von Objekten nach einer oder mehreren Eigenschaften sortieren, entweder aufsteigend (Standard) oder absteigend für jede Eigenschaft, und Sie können auswählen, ob Vergleiche zwischen Groß- und Kleinschreibung durchgeführt werden sollen oder nicht. Standardmäßig führt diese Funktion Sortierungen ohne Berücksichtigung der Groß- und Kleinschreibung durch.

Das erste Argument muss das Array sein, das die Objekte enthält. Die nachfolgenden Argumente müssen eine durch Kommas getrennte Liste von Zeichenfolgen sein, die auf die verschiedenen Objekteigenschaften verweisen, nach denen sortiert werden soll. Das letzte Argument (das optional ist) ist ein Boolescher Wert, mit dem ausgewählt wird, ob Sortierungen mit trueGroß- und Kleinschreibung durchgeführt werden sollen oder nicht .

Die Funktion sortiert jede Eigenschaft / jeden Schlüssel standardmäßig in aufsteigender Reihenfolge. Wenn Sie möchten, dass ein bestimmter Schlüssel in absteigender Reihenfolge sortiert wird, übergeben Sie stattdessen ein Array in diesem Format : ['property_name', true].

Hier sind einige Anwendungsbeispiele für die Funktion, gefolgt von einer Erklärung (wobei homessich ein Array mit den Objekten befindet):

objSort(homes, 'city') -> nach Stadt sortieren (aufsteigend, Groß- und Kleinschreibung nicht berücksichtigen)

objSort(homes, ['city', true]) -> nach Stadt sortieren (absteigend, Groß- und Kleinschreibung nicht berücksichtigt)

objSort(homes, 'city', true)-> nach Stadt sortieren, dann Preis (aufsteigend, Groß- und Kleinschreibung beachten )

objSort(homes, 'city', 'price') -> nach Stadt sortieren, dann Preis (beide aufsteigend, Groß- und Kleinschreibung nicht berücksichtigt)

objSort(homes, 'city', ['price', true]) -> nach Stadt sortieren (aufsteigend), dann Preis (absteigend), Groß- und Kleinschreibung beachten)

Und ohne weiteres, hier ist die Funktion:

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

Und hier einige Beispieldaten:

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

Dies ist ein kompletter Cheat, aber ich denke, dass er dieser Frage einen Mehrwert verleiht, da es sich im Grunde um eine vordefinierte Bibliotheksfunktion handelt, die Sie sofort verwenden können.

Wenn Ihr Code Zugriff auf lodashoder eine lodash-kompatible Bibliothek wie diese underscorehat, können Sie die _.sortByMethode verwenden. Das folgende Snippet wird direkt aus der lodash-Dokumentation kopiert .

Die kommentierten Ergebnisse in den Beispielen sehen so aus, als würden sie Arrays von Arrays zurückgeben, aber das zeigt nur die Reihenfolge und nicht die tatsächlichen Ergebnisse, die ein Array von Objekten sind.

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

Hier ist eine andere, die Ihrer Idee für die Syntax vielleicht näher kommt

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/


Bearbeiten: Nur zum Spaß, hier ist eine Variante , die nur eine SQL-ähnliche Zeichenfolge benötigt, damit Sie dies tun könnensortObjects(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);
    });
}

Diese Lösung ist sauber, aber aufgrund des Array-Vergleichs nicht performant. Sie können einfach einen Blick durch die Eigenschaften werfen und den verglichenen Wert verfolgen. Der Wert ist nicht Null. das ist viel schneller.
Amankapur91

4

Einfacher:

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

Ich mag den Ansatz von SnowBurnt, aber es bedarf einer Optimierung, um die Gleichwertigkeit der Stadt zu testen, KEIN Unterschied.

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

3

Hier ist eine generische mehrdimensionale Sortierung, die das Umkehren und / oder Zuordnen auf jeder Ebene ermöglicht.

Geschrieben in Typoskript. Informationen zu Javascript finden Sie in dieser JSFiddle

Der Code

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

Anwendungsbeispiele

Sortieren eines Personenarrays nach Nachname und Vorname:

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

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

Sortieren Sie Sprachcodes nach ihrem Namen , nicht nach ihrem Sprachcode (siehe map), und dann nach absteigender Version (siehe 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

Ein dynamischer Weg, dies mit MULTIPLE-Tasten zu tun:

  • Filtern Sie eindeutige Werte aus jeder Spalte / jedem Schlüssel der Sortierung
  • in Ordnung bringen oder umkehren
  • Fügen Sie für jedes Objekt Gewichte mit der Breite Null hinzu, basierend auf den Schlüsselwerten von indexOf (value)
  • sortieren mit caclutated Gewichten

Geben Sie hier die Bildbeschreibung ein

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

Verwenden:

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

Geben Sie hier die Bildbeschreibung ein


1

Hier ist eine generische Version der @ Snowburnt-Lösung:

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


})

Dies basiert auf einer Sortierroutine, die ich verwende. Ich habe diesen speziellen Code nicht getestet, daher kann es zu Fehlern kommen, aber Sie haben die Idee. Die Idee ist, nach dem ersten Feld zu sortieren, das einen Unterschied anzeigt, und dann anzuhalten und zum nächsten Datensatz zu wechseln. Wenn Sie also nach drei Feldern sortieren und das erste Feld im Vergleich ausreicht, um die Sortierreihenfolge der beiden zu sortierenden Datensätze zu bestimmen, geben Sie das Sortierergebnis zurück und fahren Sie mit dem nächsten Datensatz fort.

Ich habe es (eigentlich mit einer etwas komplexeren Sortierlogik) an 5000 Datensätzen getestet und es hat es im Handumdrehen geschafft. Wenn Sie tatsächlich mehr als 1000 Datensätze auf den Client laden, sollten Sie wahrscheinlich die serverseitige Sortierung und Filterung verwenden.

Dieser Code behandelt nicht die Groß- und Kleinschreibung, aber ich überlasse es dem Leser, diese triviale Änderung zu behandeln.


1

Hier ist meine Lösung, die auf der Schwartzschen Transformationssprache basiert. Ich hoffe, Sie finden sie nützlich.

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

Hier ist ein Beispiel für die Verwendung:

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
Ich habe ein paar Dinge auf dieser (und anderen Seiten) ausprobiert. Diese Lösung von a8m war nur eine, die für meine Situation geeignet war
Christopher D. Emerson

1

Ein anderer Weg

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

Verwendung des (Put- (Minus-) Zeichens vor dem Feld, wenn Sie ein bestimmtes Feld in absteigender Reihenfolge sortieren möchten)

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

Mit der obigen Funktion können Sie jedes JSON-Array mit mehreren Feldern sortieren. Der Funktionskörper muss überhaupt nicht geändert werden


0

Anpassung der Antwort von @chriskelly.


Die meisten Antworten übersehen, dass der Preis nicht richtig sortiert wird, wenn der Wert bei Zehntausenden und darunter oder über einer Million liegt. Der Grund für JS ist alphabetisch sortiert. Es war ziemlich gut hier beantwortet, Warum kann JavaScript Art „5, 10, 1“ und hier , wie ein Array von ganzen Zahlen richtig zu sortieren .

Letztendlich müssen wir eine Bewertung vornehmen, wenn das Feld oder der Knoten, nach dem wir sortieren, eine Zahl ist. Ich sage nicht, dass die Verwendung parseInt()in diesem Fall die richtige Antwort ist, die sortierten Ergebnisse sind wichtiger.

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>


Eine Geige zum Testen


Das Problem liegt bei den Daten, die Sie sortieren möchten. priceim Beispiel ist im String-Format. Wenn Sie möchten, dass es mit meinem Beispiel richtig funktioniert, verwenden Sie map, um das Feld zu konvertieren, das Sie zuerst im Zahlenformat formatieren möchten. dhconst correctedHomes = homes.map(h => ({...h, price: +h.price}))
chriskelly

0

Wow, hier gibt es einige komplexe Lösungen. So komplex, dass ich mich für etwas Einfacheres, aber auch ziemlich Mächtiges entschieden habe. Hier ist es;

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

Und hier ist ein Beispiel, wie Sie es verwenden.

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

Dies wird zuerst nach der Priorität der Attribute und dann nach dem Wert der Attribute sortiert.


0

Hier ist eine erweiterbare Möglichkeit, nach mehreren Feldern zu sortieren.

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

Anmerkungen

  • a.localeCompare(b)wird allgemein unterstützt und kehrt -1,0,1 wenn a<b, a==b, a>bbzw..
  • Die Subtraktion funktioniert bei numerischen Feldern.
  • ||in der letzten Zeile gibt cityVorrang vor price.
  • Negieren Sie, um die Reihenfolge in einem beliebigen Feld umzukehren, wie in -price_order
  • Datum Vergleich , var date_order = new Date(left.date) - new Date(right.date);funktioniert wie Numerik , weil Datum Mathematik seit 1970 in Millisekunden schaltet.
  • Fügen Sie der Or-Kette Felder hinzu. return city_order || -price_order || date_order;

0

Ich denke, dies ist vielleicht der einfachste Weg, dies zu tun.

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

Es ist wirklich einfach und ich habe es mit 3 verschiedenen Schlüsselwertpaaren versucht und es hat großartig funktioniert.

Hier ist ein einfaches Beispiel. Weitere Informationen finden Sie unter dem Link

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

Hier ist meine als Referenz, mit Beispiel:

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

Ich suchte nach etwas Ähnlichem und endete damit:

Zuerst haben wir eine oder mehrere Sortierfunktionen, die immer entweder 0, 1 oder -1 zurückgeben:

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

Sie können weitere Funktionen für jede andere Eigenschaft erstellen, nach der Sie sortieren möchten.

Dann habe ich eine Funktion, die diese Sortierfunktionen zu einer kombiniert:

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

Dies kann verwendet werden, um die oben genannten Sortierfunktionen lesbar zu kombinieren:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

Wenn eine Sortierfunktion 0 zurückgibt, wird die nächste Sortierfunktion zur weiteren Sortierung aufgerufen.


0

Nur eine weitere Option. Verwenden Sie die folgende Dienstprogrammfunktion:

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

Anwendungsbeispiel (in Ihrem Fall):

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

Es sollte beachtet werden, dass diese Funktion noch allgemeiner sein kann, um verschachtelte Eigenschaften wie 'address.city' oder 'style.size.width' usw. verwenden zu können.


0

Dies ist ein rekursiver Algorithmus zum Sortieren nach mehreren Feldern, während die Möglichkeit besteht, Werte vor dem Vergleich zu formatieren.

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

Wenn a und b gleich sind, wird nur das nächste Feld ausprobiert, bis keines mehr verfügbar ist.


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

Warum nicht einfach alles in einer einzigen Funktion zusammenfassen? Wenn die Stadt nicht gleich ist, geben Sie den Unterschied zurück, andernfalls den Preis.
Mad Physicist

-1

Hier sind 'AffiliateDueDate' und 'Title' Spalten, die beide in aufsteigender Reihenfolge sortiert sind.

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

Sortieren nach zwei Datumsfeldern und einem Beispiel für ein numerisches Feld:

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

Verwenden von:

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


-1

Wie wäre es mit dieser einfachen Lösung:

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 
}

Basierend auf dieser Frage sortiert Javascript das Array nach mehreren (Zahlen-) Feldern

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.