I am currently working on an English translation. If you like to help to proofread please contact me: ullenboom ät g m a i l dot c o m.

Java Videotraining Werbung

1. Java Stream-API

Die Stream-API ermöglicht eine schrittweise Verarbeitung von Daten. Nachdem eine Quelle Daten emittiert, folgen unterschiedliche Schritte, die Daten filtern und transformieren und auf ein Ergebnis reduzieren.

Streams — auf Deutsch auch »Ströme« genannt, wobei die Bezeichnung mehrdeutig ist und mit Ein-/Ausgabeströmen verwechselt werden könnte — sind eine wichtige Neuerung von Java 8 und greifen auf andere Neuerungen in der Java SE-Bibliothek wie vordefinierte funktionale Schnittstellen oder Optional zurück. Zusammen mit Lambda-Ausdrücken und Methodenreferenzen ergeben sich kompakter Code und eine ganz neue Art, deklarativ Verarbeitungsschritte zu konfigurieren.

Die erste Aufgabe in diesem Aufgabenblock nutzt die Helden, die wir im Kapitel über die Klassenbibliothek schon einmal kennengelernt haben. Für diese Heldensammlung kommen alle wichtigen terminalen und intermediären Operationen zum Einsatz. Es folgen unterschiedliche Aufgaben, deren Lösung die Eleganz der Stream-API zeigen.

Voraussetzungen

  • Stream aufbauen können

  • terminale und intermediäre Operationen einsetzen können

  • primitive Ströme beherrschen

  • Lambda-Ausdrücke praktisch einsetzen können

Verwendete Datentypen in diesem Kapitel:

1.1. Reguläre Ströme mit ihren terminalen und intermediären Operationen

Bei jedem Stream gibt es zwei verpflichtende Schritte und beliebig viele optionale Schritte dazwischen:

  1. Aufbau des Streams aus einer Datenquelle

  2. optionale Verarbeitungsschritte, genannt intermediäre Operationen

  3. abschließende Operation, genannt terminale Operation

1.1.1. Heldenepos: Stream-API kennenlernen ⭐

In Kapitel Die Java Klassenbibliothek wurde die Klasse Heroes mit Helden vorgestellt. Darauf greift diese Aufgabe zurück.

Stream-Aufbau:

  • Baue für die folgenden Aufgabenpunkte immer einen neuen Stream mit den Helden auf, und wende anschließend die terminalen und intermediären Operationen nach folgendem Muster an:

    Heroes.ALL.stream().intermediate1(...).intermediate2(...).terminal()

Terminale Operationen:

  1. Gib alle Informationen über Helden im CSV-Format auf dem Bildschirm aus.

  2. Frage, ob alle Helden nach 1900 eingeführt wurden.

  3. Frage, ob irgendein weiblicher Held nach 1950 (inklusiv) eingeführt wurde.

  4. Welcher Held taucht als erster auf?

  5. Welcher Held liegt beim Erscheinungsjahr am nächsten an 1960? Es soll auf dem Stream nur eine einzige terminale Operation genutzt werden.

  6. Ein StringBuilder soll entstehen, der alle Jahreszahlen kommasepariert enthält. Das Ergebnis soll mit einer einzigen terminalen Stream-Methode entstehen, keiner intermediären Operation dazwischen. Die Reihenfolge der Jahreszahlen im String spielt keine Rolle.

  7. Teile die männlichen und weiblichen Helden in zwei Gruppen auf. Das Ergebnis soll vom Typ Map<Sex, List<Hero>> sein.

  8. Bilde zwei Partitionen mit Helden, die vor und nach 1970 eingeführt wurden. Das Ergebnis soll vom Typ Map<Boolean, List<Hero>> sein.

Intermediäre (nichtterminale) Operationen:

  1. Wie viele weibliche Helden gibt es insgesamt?

  2. Sortiere alle Helden nach dem Erscheinungsdatum, und gib dann alle Helden aus.

  3. Gehe folgende Schritte durch:

    a) Erzeuge einen kommaseparierten String mit den Namen aller weiblichen Helden.
    b) Im Hero gibt es keinen Setter, weil der Hero immutable ist. Aber mit dem Konstruktor können wir neue Helden aufbauen. Konvertiere die Helden in eine Liste anonymer Helden, in denen der Klarname in Klammern zusammen mit den Klammern selbst entfernt wird.
    c) Erzeuge ein int[] mit allen Jahren, in denen Helden eingeführt wurden — ohne doppelte Einträge.

  4. Gehe über UNIVERSES und nicht über ALL um die Namen aller Helden auszugeben.

1.1.2. Den geliebten Captain aus einer Liste ermitteln ⭐

Am Ende des Jahres stimmt die Schiffsbesatzung darüber ab, welche Kandidatin oder welcher Kandidat für die Position des Captains ihnen in Zukunft den Weg zu reicher Beute ebnen sollen. Gewinner ist die Person mit den meisten Nennungen.

Aufgabe:

  • Gegeben ist ein Array von Strings mit Namen. Welcher Name wurde wie häufig genannt? Groß-/Kleinschreibung spielt bei den Namen keine Rolle.

  • Viele nennen Captain CiaoCiao einfach nur CiaoCiao, das soll gleichbedeutend sein mit Captain CiaoCiao.

Beispiel:

{ "Anne", "Captain CiaoCiao", "Balico", "Charles", "Anne", "CiaoCiao", "CiaoCiao", "Drake", "Anne", "Balico", "CiaoCiao" }{charles=1, anne=3, drake=1, ciaociao=4, balico=2}

1.1.3. Bilder einrahmen ⭐

Captain CiaoCiao ist als bester Captain gewählt worden, die Freude ist groß. Er möchte sein Bild eingerahmt haben.

Gegeben ist ein mehrzeiliger String, wie

     ______
_.-':::::::`.
\::::::::::::`.-._
 \:::''   `::::`-.`.
  \         `:::::`.\
   \          `-::::`:
    \______       `:::`.
    .|_.-'__`._     `:::\
   ,'`|:::|  )/`.     \:::
  /. -.`--'  : /.\     ::|
  `-,-'  _,'/| \|\\    |:|
   ,'`::.    |/>`;'\   |:|
   (_\ \:.:.:`((_));`. ;:|
   \.:\ ::_:_:_`-','  `-:|
    `:\\|     SSt:
       )`__...---'

Dieser soll in einen Rahmen gesetzt werden:

+------------------------------+
|                              |
|       ______                 |
|  _.-':::::::`.               |
|  \::::::::::::`.-._          |
|   \:::''   `::::`-.`.        |
|    \         `:::::`.\       |
|     \          `-::::`:      |
|      \______       `:::`.    |
|      .|_.-'__`._     `:::\   |
|     ,'`|:::|  )/`.     \:::  |
|    /. -.`--'  : /.\     ::|  |
|    `-,-'  _,'/| \|\\    |:|  |
|     ,'`::.    |/>`;'\   |:|  |
|     (_\ \:.:.:`((_));`. ;:|  |
|     \.:\ ::_:_:_`-','  `-:|  |
|      `:\\|     SSt:          |
|         )`__...---'          |
|                              |
+------------------------------+

Aufgabe:

  • Schreibe eine Methode frame(String), die einen mehrzeiligen String umrahmt. Nutze die String-Methoden lines() und repeat(…​).

    • Die horizontalen Striche bestehen aus -.

    • Die vertikalen Striche bestehen aus |.

    • In den Ecken sitzen Pluszeichen +.

    • Der Abstand rechts und links vom Rahmen beträgt 2 Leerzeichen.

    • Der innere Abstand oben und unten ist eine freie Zeile.

    • Zeilenumbrüche sind \n, sie sollen sich aber relativ leicht im Programm ändern lassen.

Java 8 Backport

lines() und repeat() wurden in Java 11 eingeführt; wer die Aufgabe unter Java 8 realisieren möchte, kann das Zerlegen in Zeilen zum Beispiel mit split("\n") lösen und das Wiederholen von Zeichen mit einer Schleife.

1.1.4. Schau und sag ⭐⭐

Captain CiaoCiao träumt vor sich hin und schreibt auf ein Stück Papier:

1

Er sieht die Eins und sagt sich: »Oh, 1-mal die 1!« Das schreibt er hin:

1 1

Jetzt sieht er zwei Einsen und kann es so vorlesen:

2 1

»Arrr! Das ist doch einmal die Zwei und einmal die Eins!« Er schreibt es hin:

1 2 1 1

Er liest die Zahlen erneut vor und sagt:

1 1 1 2 2 1

Jetzt kommt die Eins sogar dreimal vor:

3 1 2 2 1 1

Captain CiaoCiao findet, dass die Zahlen aber schnell groß werden. Er ist neugierig, ob nach ein paar Durchläufen nur die 1, 2 und 3 als Ziffern vorkommen.

Aufgabe:

  • Erzeuge mit einem Stream.iterate(…​) einen unendlichen Stream von Schau-und-sag-Zahlen.

  • Begrenze den Stream auf 20 Elemente

  • Gib die Zahlen auf der Konsole aus; sie können auch kompakt als String wie 111221 ausgegeben werden.

Die Aufgabe lässt sich mit einem geschickten Regulären Ausdruck mit einer Back-Reference lösen. Diese Lösungsvariante ist aber anspruchsvoll, und wer diesen Weg einschlagen möchte, findet unter https://regular-expressions.mobi/backref.html weitere Details.

Was hier gefragt wird, ist die Look-and-Say-Sequenz, die https://oeis.org/A005150 mit vielen Verweisen ausführlicher erklärt.

1.1.5. Doppelte Inseln mit Metallen der Seltenen Erden entfernen ⭐⭐⭐

Das Geschäft mit Metallen der Seltenen Erden ist für Bonny Brain besonders attraktiv. Ihre Crew stellt eine Liste zusammen, auf welchen Inseln welche Metalle der Seltenen Erden vorkommen. Das Ergebnis kommt in eine Textdatei, die so aussieht:

Balancar
Erbium
Benecia
Yttrium
Luria
Thulium
Kelva
Neodym
Mudd
Europium
Tamaal
Erbium
Varala
Gadolinium
Luria
Thulium

In einer Zeile steht die Insel, in der nächsten Zeile stehen die Metalle der Seltenen Erden. Allerdings kann es passieren, dass verschiedene Crewmitglieder gleiche Paare in die Textdatei eintragen. Im Beispiel ist es das Paar Luria und Thulium.

Aufgabe:

  • Schreibe ein Programm, das alle doppelten Zeilenpaare aus dem Text löscht.

  • Das Programm muss so flexibel sein, dass die Eingabe aus einem String, File, InputStream oder Path kommen kann.

  • Die Zeilen sind immer nur mit einem \n getrennt. Auch die letzte Zeile endet mit einem \n.

Für die Lösung helfen die Typen Pattern, Scanner und MatchResult sowie die Scanner-Methode findAll(…​) und weitere Stream-Methoden.

1.1.6. Wo gibt es die Segel? ⭐⭐

Bonny Brain benötigt für das Schiff ein neues Hochleistungssegel. Die Sachbearbeiter aus der Materialwirtschaft bereiten eine Liste mit Koordinaten von geeigneten Tuchmachern vor:

Point.Double[] targets = { // Latitude, Longitude
   new Point.Double( 44.7226698,  1.6716612 ),
   new Point.Double( 50.4677807, -1.5833018 ),
   new Point.Double( 44.7226698,  1.6716612 )
};

Aufgabe:

  • In der Liste kommen einige Koordinaten doppelt vor, diese können ignoriert werden.

  • Am Ende soll eine Map<Point.Double, Integer> stehen mit der Koordinate und dem Abstand in Kilometer zum aktuellen Standort von Bonny Brain (40.2390577, 3.7138939).

Eine Beispielausgabe könnte wie folgt aussehen:

{Point2D.Double[50.4677807, -1.5833018]=1209, Point2D.Double[44.7226698, 1.6716612]=525}

Der Abstand in Kilometer berechnet sich mit der Haversine-Formel so:

private static int distance( double lat1, double lng1,
                             double lat2, double lng2 ) {
  double earthRadius = 6371; // km
  double dLat = Math.toRadians( lat2 - lat1 );
  double dLng = Math.toRadians( lng2 - lng1 );
  double a = Math.sin( dLat / 2 ) * Math.sin( dLat / 2 ) +
      Math.cos( Math.toRadians( lat1 ) ) * Math.cos( Math.toRadians( lat2 ) ) *
          Math.sin( dLng / 2 ) * Math.sin( dLng / 2 );
  double d = 2 * Math.atan2( Math.sqrt( a ), Math.sqrt( 1 - a ) );
  return (int) (earthRadius * d);
}

1.1.7. Das beliebteste Auto kaufen ⭐⭐⭐

Captain CiaoCiao muss seinen Fuhrpark vergrößern, und so fragt er die Crew, welche gepanzerten Autos empfohlen werden. Er bekommt ein Array von Modellnamen der folgenden Art:

String[] cars = {
  "Gurkha RPV", "Mercedes-Benz G 63 AMG", "BMW 750", "Toyota Land Cruiser",
  "Mercedes-Benz G 63 AMG", "Volkswagen T5", "BMW 750", "Gurkha RPV", "Dartz Prombron",
  "Marauder", "Gurkha RPV" };

Aufgabe:

  • Schreibe ein Programm, das ein Array mit Modellnamen verarbeitet und am Ende eine Map<String, Long> erzeugt, das die Modellnamen mit der Anzahl Vorkommen assoziiert. Dieser Aufgabenteil kann gut mit der Stream-API gelöst werden.

  • Es soll keine Modelle geben, die nur einmal genannt wurden; erst Modelle ab zwei Nennungen sollen in der Datenstruktur auftauchen. Bei diesem Aufgabenteil können wir besser auf die Stream-API verzichten und eine andere Variante heranziehen.

Eine Beispielausgabe könnte so aussehen:

{Mercedes-Benz G 63 AMG=2, BMW 750=2, Gurkha RPV=3}

Modifiziere die Abfrage so, dass zwar alle Modelle in einer Map stehen, aber die Namen mit false assoziiert sind, wenn es weniger als zwei Nennungen gibt. Eine Ausgabe könnte so aussehen:

{Marauder=false, Dartz Prombron=false, Mercedes-Benz G 63 AMG=true, Toyota Land Cruiser=false, Volkswagen T5=false, BMW 750=true, Gurkha RPV=true}

1.2. Primitive Ströme

Neben den Streams für Objekte bietet die Java-Standard-Bibliothek drei besondere Ströme für primitive Datentypen: IntStream, LongStream und DoubleStream. Viele Methoden sind ähnlich, wichtige Unterschiede sind Bereiche (engl. range) und spezielle Reduktionen, zum Beispiel auf die Summe oder den Durchschnitt.

1.2.1. NaN in einem Array erkennen ⭐

Java unterstützt beim Fließkommatyp double drei besondere Werte: Double.NaN, Double.NEGATIVE_INFINITY und Double.POSITIVE_INFINITY; entsprechende Konstanten gibt es für float in Float. Bei mathematischen Operationen muss geprüft werden, ob das Ergebnis gültig ist und kein NaN ist. Durch die arithmetischen Operationen wie Addition, Subtraktion, Multiplikation, Division ist NaN nicht zu erreichen, es sei denn, ein Operand ist NaN, aber diverse Methoden aus der Klasse Math liefern bei ungültigen Eingaben als Ergebnis NaN. Ist zum Beispiel bei den Methoden log(double a) oder sqrt(double a) das Argument a echt kleiner als Null, ist das Ergebnis NaN.

Aufgabe:

  • Schreibe eine Methode containsNan(double[]), die true zurückliefert, wenn das Array ein NaN enthält, andernfalls false.

  • Ein einziger Ausdruck soll im Rumpf der Methode reichen.

Beispiel:

double[] numbers1 = { Math.sqrt( 2 ), Math.sqrt( 4 ) };
System.out.println( containsNan( numbers1 ) );           // false

double[] numbers2 = { Math.sqrt( 2 ), Math.sqrt( -4 ) };
System.out.println( containsNan( numbers2 ) );           // true

1.2.2. Jahrzehnte erzeugen ⭐

Ein Jahrzehnt ist ein Zeitraum von zehn Jahren, egal, wann es anfängt und endet. Üblicherweise werden Jahrzehnte anhand ihrer gemeinsamen Zehnerstelle gruppiert. Die 1990er beginnen am 1. Januar 1990 und enden am 31. Dezember 1999. Diese Interpretation nennt sich 0-bis-9-Dekade. Es gibt auch die 1-bis-0-Dekade, bei der die Zählung der Jahrzehnte mit einer 1 auf der Einerstelle beginnt. Nach dieser Interpretation gehen die 1990er vom 1. Januar 1991 und enden am 31. Dezember 2000.

Aufgabe:

  • Schreibe eine Methode int[] decades(int start, int end), die alle Jahrzehnte von einem Startjahr bis zu einem Endjahr als Array liefert.

  • Es soll die 0-bis-9-Dekade verwendet werden.

Beispiele:

  • Arrays.toString( decades( 1890, 1920 ) )[1890, 1900, 1910, 1920]

  • Arrays.toString( decades( 0, 10 ) )[0, 10]

  • Arrays.toString( decades( 10, 10 ) )[10]

  • Arrays.toString( decades( 10, -10 ) )[]

1.2.3. Array mit konstantem Inhalt über Stream erzeugen ⭐

Aufgabe:

  • Schreibe eine Methode fillNewArray(int size, int value).

Beispiel:

  • Arrays.toString( fillNewArray( 3, -1 ) )[-1, -1, -1]

1.2.4. Pyramiden zeichnen ⭐

Aufgabe:

  • Erzeuge aus einer geschickten Kombination von range(…​), mapToObj(…​) und forEach(…​) die folgende Ausgabe:

        /\
       /\/\
      /\/\/\
     /\/\/\/\
    /\/\/\/\/\

    Die Pyramide ist fünf Zeilen hoch.

  • Versuche, die Aufgabe in nur einer Anweisung zu lösen, vom Aufbau der Pyramide bis zur Konsolenausgabe.

1.2.5. Buchstabenhäufigkeit eines Strings ermitteln ⭐

Eine Voraussetzung für eine Kompression ist, häufig vorkommende Folgen möglichst kurz zu repräsentieren. Wenn in einer Datei etwa 0 0 0 0 1 1 1 vorkommt, dann wird später hinterlegt: viermal eine 0, dann dreimal eine 1. Die Information über die Zahlen 0 und 1 versucht ein Kompressionsalgorithmus in sehr wenigen Bits auszudrücken. Von Vorteil ist, wenn bekannt ist, wie oft ein Symbol oder eine Folge insgesamt vorkommt, um einschätzen zu können, ob sich eine Kompression dieser Folge überhaupt lohnt. Eine Schleife könnte vorher über die Eingabe laufen und Häufigkeiten zählen.

Aufgabe:

  • Die Eingabe ist ein String. Generiere mithilfe einer geschickten Stream-Verkettung einen neuen String, der jeden Buchstaben des Ursprung-Strings enthält, gefolgt von der Häufigkeit dieses Buchstabens im gegebenen String.

  • Die Pärchen aus Buchstabe und Häufigkeit sollen durch einen Schrägstrich im Ergebnis-String getrennt werden.

  • Performance spielt keine zentrale Rolle.

Beispiele:

  • "eclectic""e2/c3/l1/e2/c3/t1/i1/c3"

  • "cccc"c4/c4/c4/c4

  • """"

1.2.6. Von 1 auf 0, von 10 auf 9 ⭐⭐

Bonny Brain möchte ein neues Boot erwerben und schickt Elaine in den Hafen, um Boote zu bewerten. Elaine schreibt ihre Bewertungen von 1 bis 10 hintereinander auf ein Papier, etwa so:

102341024

Bonny Brain bekommt die Zahlenfolge, ist aber mit der Anordnung und den Zahlen nicht zufrieden. Die Zahlen sollen erstens durch ein Komma getrennt werden und zweitens bei 0 anfangen, nicht bei 1.

Aufgabe:

  • Schreibe eine Methode String decrementNumbers(Reader), die aus einer Eingabequelle eine Zeichenfolge mit Ziffern liest und diese in einen kommaseparierten String konvertiert; alle Zahlen sollen um 1 vermindert werden. Was keine Ziffer ist, soll auch nicht in das Ergebnis kommen.

Beispiele:

  • 102341024"9, 1, 2, 3, 9, 1, 3"

  • -1"0"

  • abc123xyz456"0, 1, 2, 3, 4, 5"

1.2.7. Zwei int-Arrays zusammenführen ⭐⭐

Aufgabe:

  • Gesucht ist eine Methode, die zwei int-Arrays zusammenführt.

  • Es soll zwei überladene Methoden geben:

    • static int[] join(int[] numbers1, int[] numbers2) und

    • static int[] join(int[] numbers1, int[] numbers2, long maxSize)

      Mit dem optionalen dritten Parameter kann die maximale Anzahl Elemente des Ergebnisses reduziert werden.

Beispiele:

int[] numbers1 = { 7, 12 };
int[] numbers2 = { 51, 56, 0, 2 };
int[] result1 = join( numbers1, numbers2 );
int[] result2 = join( numbers1, numbers2, 3 );
System.out.println( Arrays.toString( result1 ) );   // [7, 12, 51, 56, 0, 2]
System.out.println( Arrays.toString( result2 ) );   // [7, 12, 51]

1.2.8. Gewinnkombinationen ermitteln ⭐⭐

Bonny Brain plant die nächste Party und bereitet ein Ringwurfspiel vor. Als Erstes stellt sie unterschiedliche Objekte auf, zum Beispiel diese zwei:

▨ ▧

Dann gibt sie den Spielern zwei Ringe in die Hand und lässt sie werfen. Geht der Ring über ein Objekt, zählt das als Gewinn. Wie viele Möglichkeiten zum Gewinnen gibt es, und wie sehen die Möglichkeiten aus? Nimmt man die beiden Objekte ▨ ▧, könnte es sein, dass ein Spieler ▨ oder ▧, oder auch beide — ▨ und ▧ — »trifft«; kein Treffer ist kein Gewinn.

Aufgabe:

  • Gegeben ist ein String mit beliebigen Zeichen aus der Basic Multilingual Plane (BMP), also U+0000 bis U+D7FF und U+E000 bis U+FFFF.

  • Erzeuge eine Liste mit allen Möglichkeiten, wie ein Spieler gewinnen kann.

Beispiel:

  • ▨▧[▧, ▨, ▨▧], aber nicht [▧, ▨, ▨▧, ▧▨]

  • ▣▢▲[▣▢▲, ▲, ▢, ▣, ▢▲, ▣▲, ▣▢]

  • MOON[OO, MN, MO, MOON, MOO, MON, M, N, OON, ON, O]

1.3. Statistiken

Die Ströme IntStream, LongStream und DoubleStream haben terminierende Methoden wie average(), count(), max(), min() und sum(). Falls allerdings nicht nur eine dieser statistischen Informationen interessant ist, sondern mehrere, lassen sich diverse Informationen in einem IntSummaryStatistics, LongSummaryStatistics oder DoubleSummaryStatistics sammeln.

1.3.1. Die schnellsten und langsamsten Paddler ⭐

Bonny Brain richtet auf der Partyinsel X Æ A-12 den jährlichen Paddelwettbewerb »Venomous Paddle Open« aus. Am Ende sollen die beste, schlechteste und Durchschnittszeit ausgegeben werden. Die Ergebnisse der Paddler sind durch folgenden Datentyp repräsentiert:

record Result( String name, double time ) { }

Aufgabe:

  • Erzeuge einen Stream von Result-Objekten. Belege einige Result-Objekte mit ausgewählten Werten zum Testen vor.

  • Gib am Ende eine kleine Statistik der Zeiten aus.

Beispiel:

Aus dem folgenden Stream …​

Stream<Result> stream = Stream.of(
      new Result( "Bareil Antos", 124.123 ), new Result( "Kimara Cretak", 434.22 ),
      new Result( "Keyla Detmer", 321.34 ), new Result( "Amanda Grayson", 143.99 ),
      new Result( "Mora Pol", 122.22 ), new Result( "Gen Rhys", 377.23 ) );

... kann die Ausgabe so aussehen:

count:   6
min:     122,22
max:     434,22
average: 253,85

Java 8 Backport

Records sind in Java 16 eingezogen. Wer ältere Versionen einsetzt, nutzt bitte eine reguläre Klasse:

class Result {
  String name; double time;
  Result( String name, double time ) { this.name = name; this.time = time; }
}

1.3.2. Median berechnen ⭐⭐

Die Typen XXXSummaryStatistics liefern mit getAverage() den arithmetischen Mittelwert. Der arithmetische Mittelwert berechnet sich aus der Summe der gegebenen Werte geteilt durch die Anzahl der Werte. Es gibt eine Reihe weiterer Mittelwerte, etwa den geometrischen Mittelwert oder den harmonischen Mittelwert.

Mittelwerte werden häufig in der Statistik genutzt, doch sie haben das Problem, dass sie anfälliger für Ausreißer sind. Die Statistik arbeitet oft mit dem Median. Der Median ist der Zentralwert, also der Wert, der in der sortieren Liste »in der Mitte« steht. Zu kleine oder zu große Zahlen stehen am Rand und sind Ausreißer und gehen in den Median nicht ein.

Ist die Anzahl der Werte ungerade, dann gibt es eine natürliche Mitte.

  • Beispiel 1: In der Liste 9, 11, 11, 11, 12 steht in der Mitte der Median 11. Wenn die Anzahl der Werte gerade ist, lässt sich der Median aus dem arithmetischen Mittel der beiden mittleren Zahlen definieren.

  • Beispiel 2: In der Liste 10, 10, 12, 12 ist der Median das arithmetische Mittel aus den Werten 10 und 12, also 11.

Aufgabe:

  • Gegeben ist ein double[] mit Messwerten. Schreibe eine Methode double median(double... values), die den Median der Array-Werte mit gerader und ungerader Anzahl berechnet.

  • Nutze zur Lösung einen DoubleStream und überlege, ob limit(…​) und skip(…​) helfen.

1.3.3. Temperaturstatistiken berechnen und Charts zeichnen ⭐⭐⭐

Bonny Brain ist gut mit Zahlen, allerdings mag sie Charts viel lieber. Grafisch lassen sich die Daten viel einfacher erfassen, als wenn sie in Textform vorliegen.

Sie bekommt eine Tabelle mit Temperaturdaten und möchte auf den ersten Blick ablesen, wann es am wärmsten ist und ein Urlaub mit der Familie gut möglich ist.

Aufgabe:

  • Gesucht ist ein Programm, das Temperaturen verarbeiten und darstellen kann. Genauer gesagt:

    • Generiere eine Liste von Zufallszahlen, die im besten Fall dem Temperaturverlauf des Jahres folgen, etwa in Form einer Sinuskurve von 0 bis π.

    • Generiere für mehrere Jahre zufällige Temperaturwerte, und speichere die Jahre mit den Werten in einem Assoziativspeicher. Nutze den Datentyp Year als Schlüssel für eine nach Jahren sortierte Map. Bonus: Die Anzahl der Tage entspricht wirklich der Anzahl der Tage in dem Jahr, also 365 oder 366.

    • Schreibe eine ASCII-Tabelle mit den Temperaturen aller Jahre auf die Konsole.

    • Gib die höchste und niedrigste Jahrestemperatur eines Jahres aus.

    • Gib die höchste, niedrigste und durchschnittliche Temperatur für einen Monat eines Jahres aus.

    • Generiere eine Datei, in der von einem Jahr die zwölf Durchschnittstemperaturen eines Monats aggregiert und visualisiert werden. Nimm folgendes HTML-Dokument als Grundlage und fülle das data-Array entsprechend.

<!DOCTYPE html><html>
<head><meta charset="UTF-8"></head>
<body>
<canvas></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.3.2/dist/chart.min.js"></script>
<script>
  const cfg = {
    type: "bar",
    data: {
     labels:"Jan. Feb. Mar. Apr. May June July Aug. Sept. Oct. Nov. Dec.".split(" "),
     datasets: [{
      label: "Average temperature",
      data: [11, 17, 21, 25, 27, 29, 29, 27, 25.6, 21.6, 17.5, 12.5],
     }]
    }
  };
  window.onload = () => new Chart(document.querySelector("canvas").getContext("2d"), cfg);
</script>
</body></html>