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:
Noch mehr Aufgaben findest du im Buch: ›Captain CiaoCiao erobert Java: Das Trainingsbuch für besseres Java. 300 Java-Workshops, Aufgaben und Übungen mit kommentierten Lösungen‹
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 ihr 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 eineNullPointerException
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
eine Zeichenfolge komplett auf einen regulären Ausdruck »matcht«,
ein Teil-String existiert und, wenn ja, herausfinden, wo, und ihn dann ersetzen.
Später werden wir mit regulären Ausdrücken auch die Trenner angeben und Zeichenfolgen 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?
endeteine 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:
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:
Lautet der String (geschrieben in der Text-Block-Syntax)
String ocr = """ 4 4 77777 11 11 4 4 22 4 4 7 111 111 4 4 2 2 4444 7 11 11 4444 2 4 7 11 11 4 2 4 7 11l1 11l1 4 2222""";
so soll das gewünschte Ergebnis
471142
sein.
Wer mit den Strings herumspielen 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 ⭐
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!"
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:
Schreibe eine Methode, die alle Zahlen im Text erkennt und in Wörter umwandelt.
Eine Methode zum »Vorlesen« findet sich zum Beispiel bei Stack Overflow (https://stackoverflow.com/questions/3911966/how-to-convert-number-to-words-in-java).
Beispiel:
99 bottles of beer make Captain CiaoCiao happy for 10 years
→ninety-nine bottles of beer make Captain CiaoCiao happy for ten years
.
Mit dem Pärchen |
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.
Beispiele:
"Harbour: 11:00 PM, entertainment districts: 1:30 a.m.!"
→Harbour: 2300, entertainment districts: 0130!"
"Get out of bed: 12:00AM, bake a cake: 12 PM."
→"Get out of bed: 0000, bake a cake: 1200"
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.
Zeile | Inhalt | optional |
---|---|---|
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:
Zeichen (Abkürzung) | Dezimal | Hexadezimal | Escape-Sequenz |
---|---|---|---|
LF |
|
|
|
CR |
|
|
|
CR LF |
|
|
|
LF CR |
|
|
|
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;Danville;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:
Zerlege den String in Wörter. Separatoren der Wörter sind Leerzeichen und Satzzeichen.
Drehe alle Wörter einzeln um.
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 sonstfalse
.
Beispiele:
1 < 2 > 1 < 10 = 10 > 2
→true
1 < 1
→false
1 <
→false
1
→true
1.3.4. Die Kuh sagt muuuh ⭐⭐
Nach ihrer Indien-Reise, verpackt Bonny Brain alle Nachrichten in Sprechblasen der folgen Art:
___________ < Aye to Aye! > ----------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||
Wenn der Text umgebrochen werden muss, sieht die Sprechblase für zweizeilige Texte so aus:
_____________________________________ / What's a pirate's favorite file type? \ \ .rar / -------------------------------------
Für mehr als zwei Zeilen haben die Sprechblasen folgenden Aufbau:
______________________________________ / Money and success don't change people; \ | they merely amplify what is already | \ there. / --------------------------------------
Der Text in der Sprachblase muss umgebrochen werden.
Viele Probleme wurden schon gelöst, und es gibt viele Utility-Bibliotheken run um Zeichenketten. Eine der Klassen ist WordUtils
aus der Bibliothek Apache Commons Lang (https://commons.apache.org/proper/commons-lang/). Aus der Klasse wollen wir wir eine Methode für eine Aufgabe nutzen.
Aufgabe, Teil 1:
Lege eine neue Klasse
WordUtils
an.Kopiere aus https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/text/WordUtils.java die beiden Methoden
public static String wrap(final String str, final int wrapLength)
public static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn)
in die eigene Klasse
Die Zeile
if ( StringUtils.isBlank( wrapOn ) )
löst einen Compilerfehler aus, ändere die Zeile inif ( wrapOn == null || wrapOn.isBlank() )
.
Aufgabe, Teil 2:
Lege eine neue Klasse
Cow
mit folgender Methoden an:public static void say( String text, Eyes eyes )
Eyes
soll ein eigener Aufzählungstyp mit Konstanten wieNORMAL
,STONED
sein, damit sich die Augentypen vonoo
in etwa anderes ändern lassen, etwa in==
oder@@
; die Kuh selbst ändert sich nicht.Die Methode soll einen beliebig langen Text annehmen, wobei der Text bei einer Breite von 40 Zeichen umgebrochen werden soll. Eigene Zeilenumbrüche sollen ignoriert und wie ein Leerzeichen behandelt werden.
1.3.5. 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 einenString
in der A1-Notation bekommt und ein Array mit zwei Elementen zurückliefert, in dem an der Position0
die Spalte und an der Position1
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.6. 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:
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 denScanner
mituseLocale(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.7. 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:
Lege eine neue Klasse
SimpleStringCompressor
an.Schreibe eine statische Methode
String compress(String)
, die Folgen von.
und-
nach dem beschriebenen Algorithmus kodiert: Erst kommt das Zeichen, dann die Anzahl.Schreibe einen Dekodierer
String decompress(String)
, der den komprimierten String wieder auspackt. Es solldecompress(compress(input))
gleichinput
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.