Sortieren ist eine super praktische JavaScript-Methode, mit der die Werte eines Arrays in einer bestimmten Reihenfolge angezeigt werden können. Ob Immobilienangebote nach Preis, Burgerläden nach Entfernung oder die besten Happy Hours in der Nähe nach Bewertung – das Sortieren von Informationsarrays ist ein häufiges Bedürfnis.
Wenn Sie dies bereits mit JavaScript in einem Projekt tun, verwenden Sie wahrscheinlich die integrierte Array-Methode .sort, die zur selben Familie von Array-Methoden gehört wie .filter, .map und .reduce.
Schauen wir uns an, wie das geht!
Eine kurze Anmerkung zu Seiteneffekten
Bevor wir auf die Details der Verwendung von .sort eingehen, gibt es ein sehr wichtiges Detail, das angesprochen werden muss. Während viele der ES5-Array-Methoden wie .filter, .map und .reduce ein neues Array zurückgeben und das Original unverändert lassen, sortiert .sort das Array direkt. Wenn dies unerwünscht ist, ist eine ES6-Technik, um dies zu vermeiden, die Verwendung des Spread-Operators, um prägnant ein neues Array zu erstellen.
const foo = ['c','b','a'];
const bar = ['x','z','y'];
const fooSorted = foo.sort();
const barSorted = [...bar].sort();
console.log({foo, fooSorted, bar, barSorted});
/*
{
"foo": [ "a", "b", "c" ],
"fooSorted": [ "a", "b", "c" ],
"bar": [ "x", "z", "y" ],
"barSorted": [ "x", "y", "z" ]
}
*/
foo und fooSorted verweisen beide auf dasselbe Array, aber bar und barSorted sind nun einzelne Arrays.
Allgemeiner Überblick
Der einzige Parameter der Methode .sort ist eine Funktion. Die Spezifikation nennt diese compareFn – ich werde sie im Rest des Beitrags als „Vergleichsfunktion“ bezeichnen. Diese Vergleichsfunktion akzeptiert zwei Parameter, die ich a und b nennen werde. a und b sind die beiden Elemente, die wir vergleichen werden. Wenn Sie keine Vergleichsfunktion bereitstellen, konvertiert das Array jedes Element in einen String und sortiert entsprechend den Unicode-Punkten.
Wenn Sie möchten, dass a zuerst im Array geordnet wird, sollte die Vergleichsfunktion eine negative Ganzzahl zurückgeben; für b eine positive Ganzzahl. Wenn Sie möchten, dass die beiden ihre aktuelle Reihenfolge beibehalten, geben Sie 0 zurück.
Wenn Sie das nicht verstehen, machen Sie sich keine Sorgen! Hoffentlich wird es mit ein paar Beispielen viel klarer.
Vergleich von Zahlen
Eine der einfachsten Rückruffunktionen, die man schreiben kann, ist ein Zahlenvergleich.
const numbers = [13,8,2,21,5,1,3,1];
const byValue = (a,b) => a - b;
const sorted = [...numbers].sort(byValue);
console.log(sorted); // [1,1,2,3,5,8,13,21]
Wenn a größer als b ist, gibt a - b eine positive Zahl zurück, also wird b zuerst sortiert.
Vergleich von Zeichenketten
Beim Vergleichen von Zeichenketten vergleichen die Operatoren > und < Werte basierend auf dem Unicode-Wert jeder Zeichenkette. Das bedeutet, dass alle Großbuchstaben „kleiner“ sind als alle Kleinbuchstaben, was zu unerwartetem Verhalten führen kann.
JavaScript *hat* eine Methode, die beim Vergleichen von Zeichenketten hilft: die Methode String.prototype.localeCompare. Diese Methode akzeptiert eine Vergleichszeichenkette, eine Locale und ein Optionenobjekt. Das Optionenobjekt akzeptiert einige Eigenschaften (alle können Sie hier einsehen), aber ich finde die nützlichste ist „sensitivity“. Diese beeinflusst, wie Vergleiche zwischen Buchstabenvariationen wie Groß-/Kleinschreibung und Akzenten funktionieren.
const strings = ['Über', 'alpha', 'Zeal', 'über', 'uber', 'Uber', 'Alpha', 'zeal'];
const sortBySensitivity = sensitivity => (a, b) => a.localeCompare(
b,
undefined, // locale string -- undefined means to use browser default
{ sensitivity }
);
const byAccent = sortBySensitivity('accent');
const byBase = sortBySensitivity('base');
const byCase = sortBySensitivity('case');
const byVariant = sortBySensitivity('variant'); // default
const accentSorted = [...strings].sort(byAccent);
const baseSorted = [...strings].sort(byBase);
const caseSorted = [...strings].sort(byCase);
const variantSorted = [...strings].sort(byVariant);
console.log({accentSorted, baseSorted, caseSorted, variantSorted});
/*
{
"accentSorted": [ "alpha", "Alpha", "uber", "Uber", "Über", "über", "Zeal", "zeal" ],
"baseSorted": [ "alpha", "Alpha", "Über", "über", "uber", "Uber", "Zeal", "zeal" ],
"caseSorted": [ "alpha", "Alpha", "über", "uber", "Über", "Uber", "zeal", "Zeal" ],
"variantSorted": [ "alpha", "Alpha", "uber", "Uber", "über", "Über", "zeal", "Zeal" ]
}
*/
Ausführen von Funktionen vor dem Vergleichen von Werten
Möglicherweise möchten Sie eine Vergleichsfunktion auf einen Wert anwenden, der aus jedem Element des Arrays abgeleitet wird. Zuerst schreiben wir eine Vergleichsfunktionsfabrik, die über das Element „mapped“ (abbildet), bevor sie die Vergleichsfunktion aufruft.
const sortByMapped = (map,compareFn) => (a,b) => compareFn(map(a),map(b));
Ein Anwendungsfall dafür ist das Sortieren basierend auf einem Attribut eines Objekts.
const purchases = [
{ name: 'Popcorn', price: 5.75 },
{ name: 'Movie Ticket', price: 12 },
{ name: 'Soda', price: 3.75 },
{ name: 'Candy', price: 5 },
];
const sortByMapped = (map,compareFn) => (a,b) => compareFn(map(a),map(b));
const byValue = (a,b) => a - b;
const toPrice = e => e.price;
const byPrice = sortByMapped(toPrice,byValue);
console.log([...purchases].sort(byPrice));
/*
[
{ name: "Soda", price: 3.75 },
{ name: "Candy", price: 5 },
{ name: "Popcorn", price: 5.75 },
{ name: "Movie Ticket", price: 12 }
]
*/
Ein weiterer Fall könnte das Vergleichen eines Datumsarrays sein.
const dates = ['2018-12-10', '1991-02-10', '2015-10-07', '1990-01-11'];
const sortByMapped = (map,compareFn) => (a,b) => compareFn(map(a),map(b));
const toDate = e => new Date(e).getTime();
const byValue = (a,b) => a - b;
const byDate = sortByMapped(toDate,byValue);
console.log([...dates].sort(byDate));
// ["1990-01-11", "1991-02-10", "2015-10-07", "2018-12-10"]
Umkehren einer Sortierung
Es gibt Fälle, in denen Sie das Ergebnis einer Vergleichsfunktion umkehren möchten. Dies unterscheidet sich subtil vom Sortieren und anschließenden Umkehren des Ergebnisses in Bezug auf die Behandlung von Gleichständen: Wenn Sie das Ergebnis umkehren, werden auch Gleichstände in der Reihenfolge umgekehrt.
Um eine höherrangige Funktion zu schreiben, die eine Vergleichsfunktion akzeptiert und eine neue zurückgibt, müssen Sie das Vorzeichen des Rückgabewerts des Vergleichs ändern.
const flipComparison = fn => (a,b) => -fn(a,b);
const byAlpha = (a,b) => a.localeCompare(b, null, { sensitivity: 'base' });
const byReverseAlpha = flipComparison(byAlpha);
console.log(['A', 'B', 'C'].sort(byReverseAlpha)); // ['C','B','A']
Ausführen einer „Tie-Breaker“-Sortierung
Es gibt Zeiten, in denen Sie eine „Tie-Breaker“-Sortierung wünschen – das heißt, eine weitere Vergleichsfunktion, die im Falle eines Gleichstands verwendet wird.
Durch die Verwendung von [].reduce können Sie ein Array von Vergleichsfunktionen zu einer einzigen zusammenführen.
const sortByMapped = map => compareFn => (a,b) => compareFn(map(a),map(b));
const flipComparison = fn => (a,b) => -fn(a,b);
const byValue = (a,b) => a - b;
const byPrice = sortByMapped(e => e.price)(byValue);
const byRating = sortByMapped(e => e.rating)(flipComparison(byValue));
const sortByFlattened = fns => (a,b) =>
fns.reduce((acc, fn) => acc || fn(a,b), 0);
const byPriceRating = sortByFlattened([byPrice,byRating]);
const restaurants = [
{ name: "Foo's Burger Stand", price: 1, rating: 3 },
{ name: "The Tapas Bar", price: 3, rating: 4 },
{ name: "Baz Pizza", price: 3, rating: 2 },
{ name: "Amazing Deal", price: 1, rating: 5 },
{ name: "Overpriced", price: 5, rating: 1 },
];
console.log(restaurants.sort(byPriceRating));
/*
{name: "Amazing Deal", price: 1, rating: 5}
{name: "Foo's Burger Stand", price: 1, rating: 3}
{name: "The Tapas Bar", price: 3, rating: 4}
{name: "Baz Pizza", price: 3, rating: 2}
{name: "Overpriced", price: 5, rating: 1}
*/
Schreiben einer zufälligen Sortierung
Möglicherweise möchten Sie ein Array „zufällig“ sortieren. Eine Technik, die ich gesehen habe, ist die Verwendung der folgenden Funktion als Vergleichsfunktion.
const byRandom = () => Math.random() - .5;
Da Math.random() eine „zufällige“ Zahl zwischen 0 und 1 zurückgibt, sollte die Funktion byRandom die Hälfte der Zeit eine positive Zahl und die andere Hälfte eine negative Zahl zurückgeben. Das scheint eine gute Lösung zu sein, aber leider, da die Vergleichsfunktion nicht „konsistent“ ist – was bedeutet, dass sie möglicherweise nicht denselben Wert zurückgibt, wenn sie mehrmals mit denselben Werten aufgerufen wird –, kann dies zu einigen unerwarteten Ergebnissen führen.
Nehmen wir zum Beispiel ein Array von Zahlen zwischen 0 und 4. Wenn diese byRandom-Funktion wirklich zufällig wäre, würde man erwarten, dass der neue Index jeder Zahl bei genügend Iterationen gleichmäßig über die Indizes verteilt ist. Der ursprüngliche Wert 0 hätte im neuen Array genauso wahrscheinlich den Index 4 wie den Index 0. In der Praxis wird diese Funktion jedoch jede Zahl auf ihre ursprüngliche Position schieben.
Siehe den Pen
Array.sort() Random 👎 von Adam Giese (@AdamGiese)
auf CodePen.
Die „Diagonale“ von links oben hat statistisch den größten Wert. Bei einer idealen und wirklich zufälligen Sortierung würden die meisten Tabellenzellen bei etwa 20 % liegen.
Die Lösung dafür ist, einen Weg zu finden, um sicherzustellen, dass die Vergleichsfunktion konsistent bleibt. Eine Möglichkeit besteht darin, den Zufallswert jeder Array-Element vor dem Vergleich zuzuordnen und ihn danach wieder zu entfernen.
const sortByMapped = map => compareFn => (a,b) => compareFn(map(a),map(b));
const values = [0,1,2,3,4,5,6,7,8,9];
const withRandom = (e) => ({ random: Math.random(), original: e });
const toOriginal = ({original}) => original;
const toRandom = ({random}) => random;
const byValue = (a,b) => a - b;
const byRandom = sortByMapped(toRandom)(byValue);
const shuffleArray = array => array
.map(withRandom)
.sort(byRandom)
.map(toOriginal);
Dies stellt sicher, dass jedes Element einen einzelnen Zufallswert hat, der nur einmal pro Element berechnet wird und nicht einmal pro Vergleich. Dies beseitigt die Sortierverzerrung zur ursprünglichen Position.
Siehe den Pen
Array.sort() Random 👍 von Adam Giese (@AdamGiese)
auf CodePen.
Moment, warum ist die Basis sortiert am logischsten? Diese bricht die Groß-/Kleinschreibungs-Kontinuität. Meiner Meinung nach scheinen sowohl die Sortierung nach Groß-/Kleinschreibung als auch nach Varianten logischer.
Fairerweise muss man sagen, dass der Autor „Für mich“ gesagt hat, womit ich übereinstimme, aber es hängt doch alles vom Kontext ab, oder?
Wenn ich zum Beispiel durch Autonamen scrolle, scanne ich lieber eine nach Basis sortierte Liste, um die gesuchten Informationen zu finden. Aber in einer anderen Situation könnte eine nach Groß-/Kleinschreibung sortierte Liste viel mehr Sinn ergeben.
Genau meine Gedanken, nichtsdestotrotz ist es nur eine Meinung.
Das ist ein informativer Artikel. Ich habe die npm-Bibliothek compose-sort geschrieben, um bei der Herausforderung der Komplexität von „Tie-Breaker“-Verhalten zu helfen. Ich wollte das nur erwähnen, falls es für jemanden hilfreich ist, der die Funktion, die Sie oben aufgeführt haben, vermeiden möchte.
Schöner Artikel. Bezüglich „*Vergleich von Zeichenketten*“ ist es, soweit ich weiß, bei einem großen Array von Zeichenketten, die Sie sortieren möchten, besser (performance-mäßig), stattdessen
Intl.Collatorund seinecompare-Methode zu verwenden.Was die „*zufällige Sortierung*“ betrifft, so scheint sie überwältigend zu sein und klingt sehr nach einem Shuffle mit zufälligem Index anstelle einer *zufälligen Sortierung*. Vermisse ich etwas?
Ich muss sagen, ich bin ein großer Fan von Ihrem Pfeilspiel. Es ist anfangs nicht sehr intuitiv zu lesen – aber sobald ich mich daran gewöhnt hatte, scheint es sehr klar zu sein.
„ ‚ü‘, ‚u‘, ‚Ü‘ und ‚U‘ sind gleichwertig“
Als jemand, dessen Muttersprache eine ist, in der der Buchstabe „ü“ existiert, finde ich diese Aussage ziemlich ignorant und grammatisch falsch.
Sowohl u als auch ü haben ihren eigenen Platz im Alphabet als separate Buchstaben, und ihr Platz bestimmt auch die Reihenfolge.
Im Ungarischen (und Deutschen) kommt ü nach u, und das fiktive Wort „üa“ wäre definitiv *nach* „uz“.
Es tut mir leid – ich meinte, dass sie in der Basis-Sortierung gleichwertig sind, nicht gleichwertig in der Sprache, aber ich erkenne, dass dies nicht sehr klar formuliert war.
Ich habe den Satz ganz entfernt, da er für den Artikel nicht notwendig war.