mitmario.dev

Sortieren

JavaScript Im Browser 3 Min Lesezeit 4 BeispieleLektion 8 von 10

Sortieren sieht nach einer Kleinigkeit aus und hat zwei Fallen, in die wirklich jeder einmal tritt. Beide sind schnell erklärt und danach nie wieder ein Problem.

Die erste Falle: der Standard sortiert Text

sort sortiert als Text
// Bei Texten sieht der Standard richtig aus.
console.log(["Cäsar", "Anton", "Berta"].slice().sort());

// Bei Zahlen nicht. sort wandelt jeden Eintrag in Text um und
// vergleicht dann Zeichen für Zeichen.
console.log([10, 9, 100].slice().sort());

// Denn als Text verglichen kommt "10" vor "9", genau wie
// "Anton" vor "Berta" kommt.
console.log("10" < "9");
console.log("100" < "9");

// Mit Vergleichsfunktion stimmt es.
console.log([10, 9, 100].slice().sort((a, b) => a - b));

Ohne Vergleichsfunktion wandelt sort jeden Eintrag in Text um und vergleicht dann Zeichen für Zeichen.

Bei Texten sieht das Ergebnis richtig aus. Bei Zahlen nicht: Aus [10, 9, 100] wird [10, 100, 9], denn als Text verglichen kommt "10" vor "9", genau wie "Anton" vor "Berta" kommt. Verglichen wird das erste Zeichen, und 1 liegt vor 9.

Das ist kein Fehler in JavaScript, sondern eine Entscheidung aus einer Zeit, in der Listen meistens Texte enthielten. Ändern lässt sie sich nicht mehr, ohne bestehende Seiten zu brechen.

Die Regel daraus: Bei Zahlen immer eine Vergleichsfunktion. Ausnahmslos.

Die Vergleichsfunktion

Die Vergleichsfunktion
const zahlen = [10, 9, 100, 42];

// Die Funktion bekommt zwei Einträge und antwortet mit einer Zahl:
// negativ heißt a zuerst, positiv heißt b zuerst, 0 heißt egal.
console.log("Aufsteigend:", zahlen.slice().sort((a, b) => a - b));
console.log("Absteigend:", zahlen.slice().sort((a, b) => b - a));

// Genau deshalb funktioniert die Subtraktion: sie ist negativ,
// wenn a kleiner ist, und positiv, wenn a größer ist.
console.log("9 minus 10 ergibt", 9 - 10);
console.log("100 minus 9 ergibt", 100 - 9);

// Ausgeschrieben täte es dasselbe, nur länger.
console.log(
  "Ausgeschrieben:",
  zahlen.slice().sort((a, b) => {
    if (a < b) return -1;
    if (a > b) return 1;
    return 0;
  })
);

Die Funktion bekommt zwei Einträge und antwortet mit einer Zahl:

Negativ heißt: der erste gehört nach vorn. Positiv heißt: der zweite gehört nach vorn. Null heißt: Reihenfolge egal.

Genau deshalb funktioniert (a, b) => a - b. Die Subtraktion ist negativ, wenn a kleiner ist, und positiv, wenn a größer ist. Sie beantwortet die Frage also ganz von selbst.

Merk dir zwei Zeilen: (a, b) => a - b sortiert Zahlen aufsteigend, (a, b) => b - a absteigend. Wenn du unsicher bist, welche welche ist, probier es an drei Zahlen aus. Das geht schneller als nachdenken.

Und zwar wörtlich: Tipp [3, 1, 2].sort((a, b) => a - b) in die Eingabezeile der Console. Die Antwort ist (3) [1, 2, 3]. Dreh die beiden um, und die Antwort ist (3) [3, 2, 1]. Zwei Sekunden, und du musst dir keine der beiden Zeilen merken, sondern nur, dass es sich ausprobieren lässt.

Die zweite Falle: sort verändert

sort verändert die Liste
const original = [3, 1, 2];

// sort sortiert an Ort und Stelle und gibt die Liste zusätzlich
// zurück. Beide Namen zeigen danach auf dieselbe sortierte Liste.
const sortiert = original.sort((a, b) => a - b);
console.log("Sortiert:", sortiert);
console.log("Original:", original);

// Mit einer Kopie bleibt das Original stehen.
const zweitesOriginal = [3, 1, 2];
const kopieSortiert = zweitesOriginal.slice().sort((a, b) => a - b);
console.log("Kopie sortiert:", kopieSortiert);
console.log("Original:", zweitesOriginal);

// toSorted macht dasselbe in einem Schritt.
const drittesOriginal = [3, 1, 2];
console.log("Mit toSorted:", drittesOriginal.toSorted((a, b) => a - b));
console.log("Original:", drittesOriginal);

// reverse dreht um und verändert dabei ebenfalls.
console.log("Umgedreht:", [1, 2, 3].reverse());

sort sortiert an Ort und Stelle. Die Liste selbst wird umsortiert, und zusätzlich gibt sort sie zurück.

Das führt zu einem Ergebnis, das beim ersten Mal überrascht: Auch der alte Name zeigt danach auf die sortierte Liste. Es gibt keine unsortierte Fassung mehr.

Drei Zeilen in der Eingabezeile zeigen es dir: const l = [3, 1, 2], dann l.sort(), dann l allein. Die letzte Antwort ist (3) [1, 2, 3]. Vergleich das mit toUpperCase aus Lektion 2.4, wo der ursprüngliche Text danach unverändert dastand. Bei Texten geht es gar nicht anders, bei Listen schon, und sort nutzt das aus.

sort gehört damit zur ersten Sorte aus Lektion 6.2, zusammen mit push, splice und reverse. map und filter gehören zur zweiten.

Brauchst du das Original noch, sortier eine Kopie: liste.slice().sort(...). Oder nimm toSorted, das dasselbe in einem Schritt tut und die neuere Variante ist.

Texte mit Umlauten

Texte mit Umlauten
const namen = ["Zeta", "Ärger", "Anton"];

// Der Standard vergleicht Zeichencodes, und das Ä liegt weit
// hinter dem Z. Deshalb landet Ärger am Ende.
console.log("Standard:", namen.slice().sort());

// localeCompare kennt die Sprachregeln. Es liest das Ä als A und
// vergleicht dann weiter, deshalb steht Anton vor Ärger.
console.log("Mit localeCompare:", namen.slice().sort((a, b) => a.localeCompare(b, "de")));

// Und es kennt auch die Groß- und Kleinschreibung richtig. Der
// Standard zieht alle Großbuchstaben nach vorn, deshalb landet
// banane dort hinter Birne.
const gemischt = ["banane", "Apfel", "Birne"];
console.log("Standard:", gemischt.slice().sort());
console.log("Mit localeCompare:", gemischt.slice().sort((a, b) => a.localeCompare(b, "de")));

Bei deutschen Texten hat der Standard noch ein Problem: Er vergleicht Zeichencodes, und dort liegt das Ä weit hinter dem Z. Ärger landet damit hinter Zeta, und im Telefonbuch stünde es zwischen Anton und Berta.

localeCompare kennt die Sprachregeln. Es liest das Ä als A und vergleicht dann das nächste Zeichen weiter. Deshalb steht Anton vor Ärger: Beide fangen mit A an, und n kommt vor r.

Nebenbei behandelt es auch Groß- und Kleinschreibung richtig, wo der Standard alle Großbuchstaben nach vorn zieht.

Die zweite Zeile zum Merken lautet also (a, b) => a.localeCompare(b, "de"). Sie ist bei jeder Liste deutscher Texte die richtige Wahl, und mit der Sprachangabe ist sie unabhängig davon, wie der Browser des Besuchers eingestellt ist.

Zum Mitnehmen

sort sortiert ohne Vergleichsfunktion als Text. Deshalb wird aus [10, 9, 100] die Reihenfolge [10, 100, 9], und deshalb schreibt man bei Zahlen immer (a, b) => a - b dazu.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.