web-dev-qa-db-de.com

Unterstrich: sortBy () basiert auf mehreren Attributen

Ich versuche, ein Array mit Objekten basierend auf mehreren Attributen zu sortieren. Wenn also das erste Attribut zwischen zwei Objekten gleich ist, sollte ein zweites Attribut verwendet werden, um die beiden Objekte zu kombinieren. Betrachten Sie zum Beispiel das folgende Array:

var patients = [
             [{name: 'John', roomNumber: 1, bedNumber: 1}],
             [{name: 'Lisa', roomNumber: 1, bedNumber: 2}],
             [{name: 'Chris', roomNumber: 2, bedNumber: 1}],
             [{name: 'Omar', roomNumber: 3, bedNumber: 1}]
               ];

Wenn Sie diese nach dem roomNumber-Attribut sortieren, würde ich folgenden Code verwenden:

var sortedArray = _.sortBy(patients, function(patient) {
    return patient[0].roomNumber;
});

Das funktioniert gut, aber wie gehe ich vor, damit 'John' und 'Lisa' richtig sortiert werden?

102
Christian R

sortBy sagt, dass es sich um einen stabilen Sortieralgorithmus handelt. Sie sollten in der Lage sein, zuerst nach Ihrer zweiten Eigenschaft zu sortieren und dann erneut nach Ihrer ersten Eigenschaft zu sortieren:

var sortedArray = _(patients).chain().sortBy(function(patient) {
    return patient[0].name;
}).sortBy(function(patient) {
    return patient[0].roomNumber;
}).value();

Wenn die zweite sortBy feststellt, dass John und Lisa dieselbe Raumnummer haben, werden sie in der Reihenfolge gehalten, in der sie gefunden wurden, wobei die erste sortBy auf "Lisa, John" eingestellt war.

236
Rory MacLeod

Hier ist ein harter Trick, den ich manchmal in diesen Fällen benutze: Kombiniere die Eigenschaften so, dass das Ergebnis sortierbar ist:

var sortedArray = _.sortBy(patients, function(patient) {
  return [patient[0].roomNumber, patient[0].name].join("_");
});

Wie gesagt, das ist ziemlich hackig. Um dies richtig zu machen, möchten Sie wahrscheinlich die JavaScript sort-Kernmethode tatsächlich verwenden:

patients.sort(function(x, y) {
  var roomX = x[0].roomNumber;
  var roomY = y[0].roomNumber;
  if (roomX !== roomY) {
    return compare(roomX, roomY);
  }
  return compare(x[0].name, y[0].name);
});

// General comparison function for convenience
function compare(x, y) {
  if (x === y) {
    return 0;
  }
  return x > y ? 1 : -1;
}

Natürlich sortiert this Ihr Array an Ort und Stelle. Wenn Sie eine sortierte Kopie wünschen (wie _.sortBy Sie angeben würde), klonen Sie zuerst das Array:

function sortOutOfPlace(sequence, sorter) {
  var copy = _.clone(sequence);
  copy.sort(sorter);
  return copy;
}

Aus Langeweile habe ich auch eine allgemeine Lösung geschrieben (um nach einer beliebigen Anzahl von Schlüsseln zu sortieren), auch hier: schauen Sie .

49
Dan Tao

Ich weiß, dass ich zu spät zur Party komme, aber ich wollte dies für diejenigen hinzufügen, die eine sauberere und schnellere Lösung benötigen, die bereits vorgeschlagen wurde. Sie können sortBy-Aufrufe in der Reihenfolge der unwichtigsten Eigenschaft an die wichtigste Eigenschaft ketten. Im folgenden Code erstelle ich ein neues Array von Patienten, sortiert nach Name in RoomNumber aus dem ursprünglichen Array mit dem Namen patient

var sortedPatients = _.chain(patients)
  .sortBy('Name')
  .sortBy('RoomNumber')
  .value();
28
Mike Devenney

ihr Initialisierer für Patienten ist zwar ein bisschen komisch, oder? Warum nicht Sie initialisieren Sie diese Variable so - als wahres Array von Objekten - Sie können dies mit _.flatten () und nicht als Array von Arrays einzelner Objekte tun. Vielleicht handelt es sich um Tippfehler):

var patients = [
        {name: 'Omar', roomNumber: 3, bedNumber: 1},
        {name: 'John', roomNumber: 1, bedNumber: 1},
        {name: 'Chris', roomNumber: 2, bedNumber: 1},
        {name: 'Lisa', roomNumber: 1, bedNumber: 2},
        {name: 'Kiko', roomNumber: 1, bedNumber: 2}
        ];

Ich habe die Liste anders sortiert und fügte Kiko in Lisas Bett hinzu; Nur zum Spaß und sehen, welche Änderungen gemacht würden ...

var sorted = _(patients).sortBy( 
                    function(patient){
                       return [patient.roomNumber, patient.bedNumber, patient.name];
                    });

inspiziert sortiert und du siehst das

[
{bedNumber: 1, name: "John", roomNumber: 1}, 
{bedNumber: 2, name: "Kiko", roomNumber: 1}, 
{bedNumber: 2, name: "Lisa", roomNumber: 1}, 
{bedNumber: 1, name: "Chris", roomNumber: 2}, 
{bedNumber: 1, name: "Omar", roomNumber: 3}
]

meine Antwort ist also: Verwenden Sie ein Array in Ihrer Rückruffunktion Dies ist der Antwort von Dan Tao sehr ähnlich. Ich vergesse nur den Join (vielleicht, weil ich das Array von Unique-Arrays entfernt habe.) Artikel :))
Wenn Sie Ihre Datenstruktur verwenden, wäre dies:

var sorted = _(patients).chain()
                        .flatten()
                        .sortBy( function(patient){
                              return [patient.roomNumber, 
                                     patient.bedNumber, 
                                     patient.name];
                        })
                        .value();

und ein testload wäre interessant ...

10
zobidafly

Keine dieser Antworten ist ideal als allgemeine Methode zur Verwendung mehrerer Felder in einer Sortierung. Alle oben genannten Ansätze sind ineffizient, da sie entweder das Array mehrmals sortieren müssen (was bei einer ausreichend großen Liste die Abläufe erheblich verlangsamen könnte) oder sie generieren eine große Anzahl von Müllobjekten, die der VM bereinigen muss (und letztendlich das Programm verlangsamen). 

Hier ist eine Lösung, die schnell und effizient ist und leicht eine umgekehrte Sortierung ermöglicht. Sie kann mit underscore oder lodash oder direkt mit Array.sort verwendet werden.

Der wichtigste Teil ist die compositeComparator-Methode, die ein Array von Komparatorfunktionen verwendet und eine neue Komparatorfunktion zurückgibt.

/**
 * Chains a comparator function to another comparator
 * and returns the result of the first comparator, unless
 * the first comparator returns 0, in which case the
 * result of the second comparator is used.
 */
function makeChainedComparator(first, next) {
  return function(a, b) {
    var result = first(a, b);
    if (result !== 0) return result;
    return next(a, b);
  }
}

/**
 * Given an array of comparators, returns a new comparator with
 * descending priority such that
 * the next comparator will only be used if the precending on returned
 * 0 (ie, found the two objects to be equal)
 *
 * Allows multiple sorts to be used simply. For example,
 * sort by column a, then sort by column b, then sort by column c
 */
function compositeComparator(comparators) {
  return comparators.reduceRight(function(memo, comparator) {
    return makeChainedComparator(comparator, memo);
  });
}

Sie benötigen auch eine Vergleichsfunktion, um die Felder zu vergleichen, nach denen Sie sortieren möchten. Die Funktion naturalSort erstellt einen Komparator für ein bestimmtes Feld. Das Schreiben eines Komparators für die Rückwärtssortierung ist ebenfalls trivial.

function naturalSort(field) {
  return function(a, b) {
    var c1 = a[field];
    var c2 = b[field];
    if (c1 > c2) return 1;
    if (c1 < c2) return -1;
    return 0;
  }
}

(Der gesamte Code ist bisher wiederverwendbar und könnte zum Beispiel im Utility-Modul aufbewahrt werden.)

Als Nächstes müssen Sie den Verbundvergleicher erstellen. Für unser Beispiel würde es so aussehen:

var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]);

Dies wird nach Raumnummer sortiert, gefolgt vom Namen. Das Hinzufügen zusätzlicher Sortierkriterien ist trivial und hat keinen Einfluss auf die Leistung der Sortierung.

var patients = [
 {name: 'John', roomNumber: 3, bedNumber: 1},
 {name: 'Omar', roomNumber: 2, bedNumber: 1},
 {name: 'Lisa', roomNumber: 2, bedNumber: 2},
 {name: 'Chris', roomNumber: 1, bedNumber: 1},
];

// Sort using the composite
patients.sort(cmp);

console.log(patients);

Gibt Folgendes zurück

[ { name: 'Chris', roomNumber: 1, bedNumber: 1 },
  { name: 'Lisa', roomNumber: 2, bedNumber: 2 },
  { name: 'Omar', roomNumber: 2, bedNumber: 1 },
  { name: 'John', roomNumber: 3, bedNumber: 1 } ]

Ich bevorzuge diese Methode deshalb, weil sie eine schnelle Sortierung nach einer beliebigen Anzahl von Feldern zulässt, nicht viel Abfall erzeugt oder String-Verkettungen innerhalb der Sortierung durchführt und leicht verwendet werden kann, so dass einige Spalten umgekehrt sortiert werden, während die Reihenfolge der Spalten normalerweise ist Sortieren.

5

Einfaches Beispiel from http://janetriley.net/2014/12/sort-on-multiple-keys-with-underscores-sortby.html (mit freundlicher Genehmigung von @MikeDevenney)

Code

var FullySortedArray = _.sortBy(( _.sortBy(array, 'second')), 'first');

Mit Ihren Daten

var FullySortedArray = _.sortBy(( _.sortBy(patients, 'roomNumber')), 'name');
3

Vielleicht sind Underscore.js oder nur Javascript-Engines jetzt anders als zu dem Zeitpunkt, als diese Antworten geschrieben wurden, aber ich konnte dieses Problem lösen, indem ich einfach ein Array der Sortierschlüssel zurückgab.

var input = [];

for (var i = 0; i < 20; ++i) {
  input.Push({
    a: Math.round(100 * Math.random()),
    b: Math.round(3 * Math.random())
  })
}

var output = _.sortBy(input, function(o) {
  return [o.b, o.a];
});

// output is now sorted by b ascending, a ascending

In Aktion sehen Sie bitte diese Geige: https://jsfiddle.net/mikeular/xenu3u91/

2
Mike K

Einfach ein Array von Eigenschaften zurückgeben mit dem Sie sortieren möchten:

ES6-Syntax

var sortedArray = _.sortBy(patients, patient => [patient[0].name, patient[1].roomNumber])

ES5-Syntax

var sortedArray = _.sortBy(patients, function(patient) { 
    return [patient[0].name, patient[1].roomNumber]
})

Dies hat keine Nebeneffekte bei der Umwandlung einer Zahl in eine Zeichenfolge.

1
Lucky Soni

Sie können die Eigenschaften, die Sie sortieren möchten, im Iterator verketten:

return [patient[0].roomNumber,patient[0].name].join('|');

oder etwas gleichwertiges.

HINWEIS: Da Sie das numerische Attribut roomNumber in eine Zeichenfolge konvertieren, müssen Sie etwas tun, wenn Sie Raumnummern> 10 hätten. Andernfalls wird 11 vor 2 angezeigt 1.

1
Mark Sherretta

Ich denke, Sie sollten lieber _.orderBy anstelle von sortBy verwenden:

_.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])
1
ZhangYi

Wenn Sie Angular verwenden, können Sie den Nummernfilter in der HTML-Datei verwenden, anstatt JS- oder CSS-Handler hinzuzufügen. Zum Beispiel:

  No fractions: <span>{{val | number:0}}</span><br>

In diesem Beispiel wird val = 1234567 als angezeigt 

  No fractions: 1,234,567

Beispiel und weitere Anleitungen unter: https://docs.angularjs.org/api/ng/filter/number

0
junktrunk