Tutorials › Teil 2: Daten
Kapitel 7 von 16
Listen: das Array
Sechs Lottozahlen. Als sechs Variablen wäre das schon lästig, bei neunundvierzig möglichen Werten wird es albern.
const gezogen = [7, 12, 23, 31, 38, 44];
zeige("Anzahl:", gezogen.length);
zeige("Erste: ", gezogen[0]);
zeige("Letzte:", gezogen[gezogen.length - 1]);
zeige("Auch letzte:", gezogen.at(-1));
Gezählt wird ab null. Die erste Zahl steht auf Position 0, die sechste auf Position 5. Daran gewöhnt man sich schneller, als man denkt, und es ist in fast jeder Programmiersprache so.
at(-1) zählt von hinten und spart die Rechnerei mit length.
Hinein und heraus
const liste = ["a", "b"];
liste.push("c");
zeige("nach push:", liste);
const letztes = liste.pop();
zeige("pop gab zurück:", letztes, "| Rest:", liste);
liste.unshift("Z");
zeige("nach unshift:", liste);
liste.shift();
zeige("nach shift:", liste);
push und pop arbeiten am Ende, unshift und shift am Anfang. Die vier verändern das Array, auf dem sie aufgerufen werden. Es entsteht kein neues.
Das ist der Grund, warum das hier trotz const funktioniert: Die Beschriftung der Schublade bleibt gleich, nur der Inhalt ändert sich. Kapitel 3 hat das schon angerissen.
Suchen
const anbieter = ["Telekom", "Vodafone", "o2", "PYUR"];
zeige(anbieter.includes("o2"));
zeige(anbieter.indexOf("PYUR"));
zeige(anbieter.indexOf("Congstar"));
indexOf liefert minus eins, wenn nichts gefunden wurde. Das ist eine alte Konvention aus Zeiten, in denen es includes noch nicht gab. Für die reine Ja-Nein-Frage ist includes klarer.
Die drei wichtigen Methoden
Ab hier wird es interessant. Diese drei nehmen eine Funktion entgegen und rufen sie für jeden Eintrag auf.
map: jeden Eintrag umwandeln
const preise = [9.95, 19.99, 34.5];
const mitSteuer = preise.map((p) => p * 1.19);
const alsText = preise.map((p) => p.toFixed(2) + " €");
zeige(mitSteuer);
zeige(alsText);
zeige("Original unverändert:", preise);
Heraus kommt immer ein neues Array mit genau so vielen Einträgen wie vorher. Das Original bleibt, wie es war.
filter: aussortieren
const tarife = [
{ name: "DSL 16", preis: 9.95 },
{ name: "Kabel 250", preis: 24.99 },
{ name: "Glasfaser 1000", preis: 44.5 },
];
const guenstig = tarife.filter((t) => t.preis < 30);
zeige("übrig:", guenstig.length);
for (const t of guenstig) zeige(t.name);
Die übergebene Funktion muss wahr oder falsch liefern. Bei wahr kommt der Eintrag ins neue Array, bei falsch nicht.
reduce: alles zu einem Wert
const punkte = [12, 8, 20, 5];
const summe = punkte.reduce((bisher, jetzt) => bisher + jetzt, 0);
const groesste = punkte.reduce((a, b) => (a > b ? a : b));
zeige("Summe:", summe);
zeige("Größte:", groesste);
reduce ist die sperrigste der drei. Der erste Parameter der Funktion trägt das Zwischenergebnis von Durchgang zu Durchgang weiter, der zweite ist der aktuelle Eintrag. Die Null hinter der Funktion ist der Startwert.
Für Summen und Durchschnitte lohnt sich das. Für alles andere gibt es meistens eine lesbarere Lösung.
Verketten
Der eigentliche Gewinn entsteht, wenn man die Methoden hintereinanderhängt.
const tarife = [
{ anbieter: "Telekom", preis: 9.95, technik: "DSL" },
{ anbieter: "Vodafone", preis: 19.99, technik: "Kabel" },
{ anbieter: "o2", preis: 14.99, technik: "DSL" },
{ anbieter: "PYUR", preis: 23.0, technik: "Kabel" },
];
const summeDSL = tarife
.filter((t) => t.technik === "DSL")
.map((t) => t.preis)
.reduce((a, b) => a + b, 0);
zeige("DSL-Tarife zusammen:", summeDSL.toFixed(2), "€");
Von oben nach unten gelesen: nimm die DSL-Tarife, hol daraus die Preise, addiere sie. Mit einer klassischen for-Schleife und einer Zählvariablen wäre das dreimal so lang und nicht klarer.
find, some, every
const zahlen = [4, 9, 12, 7];
zeige(zahlen.find((z) => z > 8));
zeige(zahlen.findIndex((z) => z > 8));
zeige("mindestens eine über 10?", zahlen.some((z) => z > 10));
zeige("alle über 3?", zahlen.every((z) => z > 3));
find liefert den ersten Treffer selbst, nicht ein Array mit einem Eintrag. Findet es nichts, kommt undefined zurück.
Sortieren, und der Klassiker dabei
const zahlen = [10, 9, 100, 25, 3];
zeige("so:", [...zahlen].sort());
zeige("richtig:", [...zahlen].sort((a, b) => a - b));
zeige("absteigend:", [...zahlen].sort((a, b) => b - a));
Ohne Vergleichsfunktion wandelt sort jeden Eintrag in Text um und sortiert alphabetisch. Deshalb landet die 100 vor der 25, denn als Text kommt „1" vor „2".
Die Vergleichsfunktion bekommt zwei Einträge und soll eine negative Zahl liefern, wenn der erste nach vorn gehört, eine positive, wenn er nach hinten gehört, und null bei Gleichstand. Für Zahlen erledigt a - b das.
Für Text mit Umlauten nimm localeCompare, sonst landet „Ö" hinter „Z":
const orte = ["Zwickau", "Österreich", "Aachen", "Ähre"];
zeige("naiv:", [...orte].sort());
zeige("gut: ", [...orte].sort((a, b) => a.localeCompare(b, "de")));
Die drei Punkte
Sie heißen Spread und sind an mehreren Stellen nützlich.
const a = [1, 2];
const b = [3, 4];
zeige("zusammen:", [...a, ...b]);
zeige("mit extra:", [0, ...a, 99]);
zeige("Kopie:", [...a]);
zeige("größte Zahl:", Math.max(...[4, 17, 9]));
Die letzte Zeile löst ein Problem: Math.max erwartet einzelne Argumente, kein Array. Die Punkte packen das Array in einzelne Argumente aus.
Ein echtes Beispiel
Sechs verschiedene Zahlen von 1 bis 49, wie beim Lotto. Das Werkzeug dazu liegt unter Lottozahlen Generator im Archiv.
function ziehen(anzahl, hoechste) {
const topf = [];
for (let i = 1; i <= hoechste; i++) topf.push(i);
const gezogen = [];
while (gezogen.length < anzahl) {
const platz = Math.floor(Math.random() * topf.length);
gezogen.push(topf[platz]);
topf.splice(platz, 1);
}
return gezogen.sort((a, b) => a - b);
}
zeige(ziehen(6, 49).join(" · "));
zeige(ziehen(6, 49).join(" · "));
Der Trick steckt in splice: Die gezogene Zahl fliegt aus dem Topf, damit sie kein zweites Mal kommen kann. Wer stattdessen einfach sechsmal eine Zufallszahl zieht und auf Dubletten hofft, hat irgendwann zwei gleiche Zahlen im Schein.
join fügt die Einträge zu einem Text zusammen, mit dem angegebenen Trennzeichen dazwischen.
Listen ordnen Dinge nach Position. Im nächsten Kapitel geht es um die andere Art, Daten zu bündeln: nach Namen.