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. Fortgeschrittene Zeichenkettenverarbeitung

Nachdem wir uns in einem anderen Kapitel mit den grundlegenden Datentypen rund um Zeichen und Zeichenfolgen beschäftigt haben, wollen wir nun fortschrittliche Zeichenkettenverarbeitung besprechen. Die Themen sind formatierte Ausgabe, reguläre Ausdrücke, Zerlegen von Zeichenfolgen.

Voraussetzungen

  • Strings formatieren können

  • mit regulären Ausdrücken matchen, suchen, ersetzen können

  • Strings zerlegen können

  • Zeichenkodierungen und UTF-8 verstehen

Verwendete Datentypen in diesem Kapitel:

1.1. Strings formatieren

Um Strings, Zahlen und temporale Daten in einen String zu setzen, gibt es in Java unterschiedliche Möglichkeiten. Im Paket java.text finden sich die Klassen MessageFormat, DateFormat und DecimalFormat sowie die Klasse Formatter und in String die Methode String.format(…​).Die nächsten Aufgaben lassen sich kompakt mit den Formatierungs-Strings vom Formatter lösen.

1.1.1. ASCII-Tabelle aufbauen ⭐

Bonny Brain hat auf ihrem Aye Phone eine neue App installiert, die ihr das ASCII-Alphabet zeigt:

$ ascii
Usage: ascii [-adxohv] [-t] [char-alias...]
   -t = one-line output  -a = vertical format
   -d = Decimal table  -o = octal table  -x = hex table  -b binary table
   -h = This help screen -v = version information
Prints all aliases of an ASCII character. Args may be chars, C \-escapes,
English names, ^-escapes, ASCII mnemonics, or numerics in decimal/octal/hex.

Dec Hex    Dec Hex    Dec Hex  Dec Hex  Dec Hex  Dec Hex   Dec Hex   Dec Hex
  0 00 NUL  16 10 DLE  32 20    48 30 0  64 40 @  80 50 P   96 60 `  112 70 p
  1 01 SOH  17 11 DC1  33 21 !  49 31 1  65 41 A  81 51 Q   97 61 a  113 71 q
  2 02 STX  18 12 DC2  34 22 "  50 32 2  66 42 B  82 52 R   98 62 b  114 72 r
  3 03 ETX  19 13 DC3  35 23 #  51 33 3  67 43 C  83 53 S   99 63 c  115 73 s
  4 04 EOT  20 14 DC4  36 24 $  52 34 4  68 44 D  84 54 T  100 64 d  116 74 t
  5 05 ENQ  21 15 NAK  37 25 %  53 35 5  69 45 E  85 55 U  101 65 e  117 75 u
  6 06 ACK  22 16 SYN  38 26 &  54 36 6  70 46 F  86 56 V  102 66 f  118 76 v
  7 07 BEL  23 17 ETB  39 27 '  55 37 7  71 47 G  87 57 W  103 67 g  119 77 w
  8 08 BS   24 18 CAN  40 28 (  56 38 8  72 48 H  88 58 X  104 68 h  120 78 x
  9 09 HT   25 19 EM   41 29 )  57 39 9  73 49 I  89 59 Y  105 69 i  121 79 y
 10 0A LF   26 1A SUB  42 2A *  58 3A :  74 4A J  90 5A Z  106 6A j  122 7A z
 11 0B VT   27 1B ESC  43 2B +  59 3B ;  75 4B K  91 5B [  107 6B k  123 7B {
 12 0C FF   28 1C FS   44 2C ,  60 3C <  76 4C L  92 5C \  108 6C l  124 7C |
 13 0D CR   29 1D GS   45 2D -  61 3D =  77 4D M  93 5D ]  109 6D m  125 7D }
 14 0E SO   30 1E RS   46 2E .  62 3E >  78 4E N  94 5E ^  110 6E n  126 7E ~
 15 0F SI   31 1F US   47 2F /  63 3F ?  79 4F O  95 5F _  111 6F o  127 7F DEL

Allerdings hat sein Aye Phone nicht so einen breiten Bildschirm, und die ersten zwei Blöcke sind sowieso keine sichtbaren Zeichen.

Aufgabe:

  • Schreibe ein Programm, das alle ASCII-Zeichen von Position 32 bis 127 in der gleichen Formatierung ausgibt, wie es das Unix-Programm ascii macht.

  • An die Position 127 schreibe DEL.

1.1.2. Ausgaben bündig untereinandersetzen ⭐

Captain CiaoCiao benötigt für eine Aufstellung eine Tabelle der folgenden Art:

Dory Dab     paid
Bob Banjo    paid
Cod Buri     paid
Bugsy        not paid

Aufgabe:

  • Schreibe eine Methode printList(String[] names, boolean[] paid), die eine Aufstellung auf dem Bildschirm ausgibt. Im ersten String-Array stehen alle Namen, im zweiten Array Informationen, ob die Person bezahlt hat oder nicht.

  • Alle Namen können unterschiedlich lang sein, dennoch sollen die Texte in der zweiten Spalte bündig untereinanderstehen.

  • Der längste String in der ersten Spalte hat einen Abstand von vier Leerzeichen zur zweiten Spalte.

  • Sind übergebene Arrays null, muss eine NullPointerException folgen.

1.2. Reguläre Ausdrücke und Mustererkennung

Reguläre Ausdrücke sind für viele Fluch und Segen. Falsch eingesetzt führen sie zu Programmen, die später nicht mehr lesbar sind, richtig eingesetzt verkürzen sie das Programm massiv und tragen zur Übersichtlichkeit bei.

Die nächsten Aufgaben sollen zeigen, dass wir mit regulären Ausdrücken einfach testen können, ob

  1. eine Zeichenfolge komplett auf einen regulären Ausdruck »matcht«,

  2. ein Teil-String existiert und, wenn ja, herausfinden, wo, und ihn dann ersetzen.

Später werden wir dann mit regulären Ausdrücken auch die Trenner angeben und Zeichenfolge zerlegen.

1.2.1. Quiz: Regex definieren ⭐

Wie sehen die Regex aus, damit man Folgendes matchen oder finden kann?

  • eine Zeichenfolge aus exakt 10 Ziffern

  • eine Zeichenfolge aus 5 bis 10 Ziffern und Buchstaben

  • eine Zeichenfolge, die wie ein Satz auf ., ! oder ? endet

  • eine nicht-leere Zeichenfolge, die keine Ziffern enthält

  • eine Amtsbezeichnung oder ein Namenstitel, Prof., Dr., Dr. med., Dr. h.c. im String

1.2.2. Beliebtheit in sozialen Medien ermitteln ⭐

Natürlich ist Captain CiaoCiao in den sozialen Medien aktiv, seine Kennung ist #CaptainCiaoCiao bzw. @CaptainCiaoCiao.

Jetzt möchte Captain CiaoCiao wissen, wie populär er ist.

Aufgabe:

  • Gegeben ist ein aggregierter Text mit Nachrichten: wie oft kommt dort #CaptainCiaoCiao bzw. @CaptainCiaoCiao vor?

Beispiel:

  • Bei der folgenden Eingabe ist das Ergebnis 2.

    Make me a baby #CaptainCiaoCiao
    Hey @CaptainCiaoCiao, where is the recruitment test?
    What is a hacker’s favorite pop group? The Black IP’s.

1.2.3. Eingescannte Werte erkennen ⭐

Bonny Brain bekommt eingescannte Listen mit Zahlen, die elektronisch weiterverarbeitet werden müssen. Im ersten Schritt schickt sie die Scans durch eine OCR-Erkennung, und am Ende steht ASCII-Text. Die Zahlen aus der OCR-Kennung sehen immer so aus:

Darstellung der Zahlen 0 bis 9
 000   11   22  333  4  4 5555   6   77777  888   9999
0  00 111  2  2    3 4  4 5     6       7  8   8 9   9
0 0 0  11    2   33  4444 555  6666    7    888   9999
00  0  11   2      3    4    5 6   6   7   8   8    9
 000  11l1 2222 333     4 555   666    7    888    9

Aufgabe:

  • Gegeben ist eine Zeile aus dem Scan mit Zahlen aus dem gezeigten Format. Wandele die Zahlen in eine Ganzzahl um.

  • Nach der letzten Ziffer könnten die Leerzeichen fehlen, und zwischen den großen Zeichen könnten mehrere Leerzeichen stehen.

Beispiel für das Ergebnis 471142:

String ocr = "4  4 77777  11   11    4  4  22\n" +
             "4  4    7  111  111    4  4 2  2\n" +
             "4444   7    11   11    4444   2\n" +
             "   4   7    11   11       4  2\n" +
             "   4   7   11l1 11l1      4 2222";

Wer mit den Strings rumspielen möchte, findet unter https://patorjk.com/software/taag/#p=display&f=Alphabet&t=0123456789 eine Möglichkeit.

1.2.4. Leise bitte! Schreiende Texte entschärfen ⭐ (Java 9)

Captain CiaoCiao bekommt oft Briefe, und die Absender SCHREIEN oft in Großbuchstaben — wie unangenehm für seine Augen.

Aufgabe:

  • Schreibe eine Methode String silentShoutingWords(String), die alle großgeschriebenen Wörter ab einer Länge von drei Buchstaben in Kleinbuchstaben konvertiert.

Beispiel:

  • silentShoutingWords("AY Captain! Smutje MUSS WEG!")"AY Captain! Smutje muss weg!"

Nutze (wenn möglich) eine passende Java 9-Methode, um die Aufgabe in einer Zeile Code lösen zu können. Die Aufgabe lässt etwas anders auch unter Java 8 lösen.

1.2.5. Zahlen erkennen und in Wörter umwandeln ⭐⭐

Gegeben ist ein englischer Satz mit Zahlen, etwa 99 bottles of beer make Captain CiaoCiao happy for 10 years. Alle Zahlen sollen englisch »ausgeschrieben« — also »99« als »ninety-nine«, »10« als »ten« — und in den Text eingebaut werden.

Aufgabe:

Beispiel:

  • 99 bottles of beer make Captain CiaoCiao happy for 10 yearsninety-nine bottles of beer make Captain CiaoCiao happy for ten years.

Mit dem Pärchen Pattern und Matcher ist nicht nur einfaches Suchen und Ersetzen möglich, sondern die Typen bieten weitere interessante Möglichkeiten. Interessant vom Matcher sind die Methoden appendReplacement(…​) und appendTail(…​).

1.2.6. Zeit mit AM und PM in 24-Stunden-Zählung umsetzen ⭐⭐

Bonny Brain bekommt oft englischsprachige Nachrichten, bei der die Zeit in AM und PM angeben ist.

Wir überfallen den Hafen um 11:00 PM und treffen uns auf der Amüsiermeile um 1:30 a.m.

Das mag Bonny Brain nicht, sie wünscht ausschließlich die 24-Stunden-Zählung der Military Time.

Aufgabe:

  • Schreibe einen Konverter, der Strings mit AM/PM (unabhängig von der Groß-/Kleinschreibung, auch mit Punkten) in Military Time konvertiert. Zur Erinnerung: 12:00 AM ist 00:00, und 12:00 PM ist 12:00.

Beispiel:

  • "Hafen: 11:00 PM, Amüsiermeile: 1:30 a.m.!""Hafen: 2300 Uhr, Amüsiermeile: 0130 Uhr!"

  • "Aufstehen: 12:00AM, Kuchen backen: 12 PM.""Aufstehen: 0000 Uhr, Kuchen backen: 1200 Uhr."

1.3. Zeichenketten in Tokens zerlegen

Zerlegen — auch Tokenisieren genannt — ist das Gegenteil vom Aufbauen und Formatieren. Eine Zeichenkette wird in Teil-Strings zerlegt, wobei Separatoren die Trennungspunkte bestimmen. Java bietet diverse Klassen zum Tokenisieren von Zeichenfolgen und Eingaben. Die Separatoren können Symbole oder Symbolfolgen sein oder durch reguläre Ausdrücke beschrieben werden. Die Methode split(…​) der String-Klasse arbeitet genauso wie der Scanner mit regulären Ausdrücken, die Klasse StringTokenizer nicht mit regulären Ausdrücken.

1.3.1. Adresszeilen mit dem StringTokenizer zerlegen ⭐

Die Software von Captain CiaoCiao muss Adressen auswerten, die aus drei oder vier Zeilen bestehen.

Tabelle 1. Die Bedeutung der Zeilen
ZeileInhaltoptional

1

Name

nein

2

Straße

nein

3

Ort

nein

4

Land

ja

Die Zeilen sind mit einem Zeilenumbruch getrennt, wobei es vier gültige Trennungssymbole bzw. -folgen gibt:

Tabelle 2. Zeilenumbruch
Zeichen (Abkürzung)DezimalHexadezimalEscape-Sequenz

LF

10

0A

\n

CR

13

0D

\r

CR LF

13 10

0D 0A

\r\n

LF CR

10 13

0A 0D

\n\r

LF ist die Abkürzung für »line feed« und CR für »carriage return«; bei alten Fern-schreibern bewegte CR den Wagen in die erste Spalte und, LF schob das Papier nach oben.

Traditionell benutzen DOS und Microsoft Windows die Kombination \r\n, während Unix-Systeme \n einsetzen.

Aufgabe:

  • Zerlege eine durch Zeilenumbrüche getrennte Zeichenfolge in vier Zeilen, und weise die Zeilen den Variablen name, street, city, country zu.

  • Falls eine vierte Zeile mit dem Landesnamen nicht gegeben ist, soll country gleich "Drusselstein" sein.

  • Setze die Zeile als CSV-Zeile durch Semikolons getrennt wieder zusammen.

Beispiele:

  • "Boots and Bootles\n21 Pickle Street\n424242 Douglas\nArendelle"Boots and Bootles;21 Pickle Street;424242 Douglas;Arendelle

  • "Doofenshmirtz Evil Inc.\nStrudelkuschel 4427\nDanville"Doofenshmirtz Evil Inc.;Strudelkuschel 4427;Gimmelshtump;Drusselstein

1.3.2. Sätze in Wörter zerlegen und umdrehen ⭐

Bonny Brain wartet auf eine Nachricht, doch bei der Übertragung ist etwas schiefgelaufen — alle Wörter sind umgedreht!

erehW did eht etarip esahcrup sih kooh? tA eht dnah-dnoces pohs!

Aufgabe:

  1. Zerlege den String in Wörter. Separatoren der Wörter sind Leerzeichen und Satzzeichen.

  2. Drehe alle Wörter einzeln um.

  3. Gib die Wörter hintereinander mit einem Leerzeichen getrennt aus. Die Satzzeichen und sonstigen Separatoren spielen keine Rolle.

Beispiel:

  • erehW did eht etarip esahcrup sih kooh? tA eht dnah-dnoces pohs!Where did the pirate purchase his hook At the hand second shop

1.3.3. Relationen zwischen Zahlen prüfen ⭐

Captain CiaoCiao übt das Bogenschießen, und die Punkte von 0 bis 10 trägt er in einer Liste ein. Auch vermerkt er, ob er besser oder schlechter geworden ist,oder ob die Punktzahl gleich bleibt. Das kann so aussehen:

1 < 2 > 1 < 10 = 10 > 2

Goldy Goldfish hat die Aufgabe die Relationszeichen <, > und = zu prüfen.

Aufgabe:

  • Schreibe ein Programm, das einen wie in dem Beispiel aufgebauten String bekommt und true liefert, wenn alle Relationszeichen korrekt sind, und sonst false.

Beispiele:

  • 1 < 2 > 1 < 10 = 10 > 2true

  • 1 < 1false

  • 1 <false

  • 1true

1.3.4. A1-Notation in Spalten und Zeilen umwandeln ⭐⭐

Captain CiaoCiao führt Buch über seine Beute und arbeitet mit Tabellen. Mit seinen Mitarbeitern bespricht er die Werte, und um die Zellen anzusprechen, verwendet er den Spalten- und Zeilenindex; er sagt z. B. 4-16 und meint damit die 4. Spalte und die 16. Zeile. Nun hat er von einer ganz neuen Möglichkeit gehört, Zellen zu benennen, der A1-Notation, die eine neuartige Software namens ECKSEL verwendet. Dabei kodiert man die Spalte mit Buchstaben von A bis Z, nach folgendem Schema:

A, B, ..., Z, AA, ..., AZ, BA, ..., ZZ, AAA, AAB, ...

Die Zeilen werden weiterhin mit Ziffern beschrieben. So steht A2 für die Zelle 1-2.

Da Captain CiaoCiao mit der A1-Notation seine Schwierigkeiten hat, soll die Angabe zurück in numerische Spalten und Zeilen konvertiert werden.

Aufgabe:

  • Schreibe eine Methode parseA1Notation(String), die einen String in der A1-Notation bekommt und ein Array mit zwei Elementen zurückliefert, in dem an der Position 0 die Spalte und an der Position 1 die Zeile steht.

Beispiele:

  • parseA1Notation( "A1" )[1, 1]

  • parseA1Notation( "Z2" )[26, 2]

  • parseA1Notation( "AA34" )[27, 34]

  • parseA1Notation( "BZ" )[0, 0]

  • parseA1Notation( "34" )[0, 0]

  • parseA1Notation( " " )[0, 0]

  • parseA1Notation( "" )[0, 0]

1.3.5. Einfache CSV-Dateien mit Koordinaten parsen ⭐

Bonny Brain vermerkt in einer CSV-Datei coordinates.csv die Stellen mit Beute, wobei die Koordinaten Fließkommazahlen sind, die durch Komma getrennt sind.

Die Datei sieht zum Beispiel so aus:

Listing 1. com/tutego/exercise/string/coordinates.csv
20.091612,-155.676695
23.087301,-73.643472
21.305452,-71.690421

Aufgabe:

  • Lege von Hand eine CSV-Datei an. Sie soll mehrere Zeilen mit Koordinaten beinhalten; die Koordinaten sind durch ein Komma getrennt.

  • Ein Java-Programm soll die CSV-Datei einlesen und eine HTML-Datei mit SVG für den Polygonzug auf dem Bildschirm ausgeben.

  • Greife zum Parsen der Datei auf den Scanner zurück. Achte darauf den Scanner mit useLocale(Locale.ENGLISH) zu initialisieren.

Beispiel: Für den oberen Block soll entstehen:

<svg height="210" width="500">
<polygon points="20.091612,-155.676695 23.087301,-73.643472 21.305452,-71.690421 " style="fill:lime;stroke:purple;stroke-width:1" />
</svg>

1.3.6. Strings verlustfrei durch Lauflängenkodierung komprimieren ⭐⭐⭐

Um das Datenvolumen zu reduzieren, werden Dateien oft komprimiert. Es gibt unterschiedliche Kompressionsalgorithmen: einige sind verlustbehaftet, wie das Entfernen von Vokalen, andere arbeiten ohne Verlust wie ZIP. Verlustbehaftete Kompression findet man z. B. bei Bildern, JPEG ist ein gutes Beispiel. In Abhängigkeit von dem Grad der Kompression verschlechtert sich die Bildqualität. Eine sehr hohe Kompression führt beim JPEG-Verfahren zu einem Bild mit starken Artefakten.

Eine einfache verlustfreie Kompression ist die Lauflängenkodierung. Das Prinzip dabei ist, Folge von gleichen Symbolen zusammenzufassen, sodass nur noch die Anzahl und das Symbol geschrieben werden. Das Grafikformat GIF nutzt z. B. diese Form der Kompression. Daher sind Bilder mit vielen einfarbigen Zeilen auch kleiner als zum Beispiel Bilder, in denen jeder Bildpunkt eine andere Farbe hat.

In der nächsten Aufgabe geht es um Lauflängenkodierung. Nehmen wir an, ein String besteht aus einer Folge von . (Punkt) und - (Minuszeichen), etwa:

--....--------..-

Um die Länge von Zeichenketten zu verkürzen, können wir zunächst das Symbol gefolgt von der Anzahl der Symbole schreiben. Die Zeichenfolge mit 17 Zeichen könnte auf folgende Zeichenfolge mit 9 Zeichen verkürzt werden:

-2.4-8.2-

Aufgabe:

  1. Lege eine neue Klasse SimpleStringCompressor an.

  2. Schreibe eine statische Methode String compress(String), die Folgen von . und - nach dem beschriebenen Algorithmus kodiert: Erst kommt das Zeichen, dann die Anzahl.

  3. Schreibe einen Dekodierer String decompress(String), der den komprimierten String wieder auspackt. Es soll decompress(compress(input)) gleich input sein.

Erweiterungen:

  • Das Programm soll alle Nichtziffern verarbeiten können.

  • Verfeinere das Programm so, dass die Zahl ausbleibt, wenn das Zeichen nur genau einmal vorkommt.