In der Serie "Named Problems" stelle ich dir klassische, namentlich bekannte Algorithmus-Probleme vor, die seit Jahrzehnten in Ausbildung, Studium und Interviews verwendet werden. Anhand verständlicher Problemstellungen wie FizzBuzz, Türme von Hanoi oder dem Sieb des Eratosthenes lernst du hier, algorithmisch zu denken, Lösungsstrategien zu entwickeln und diese sauber in Java umzusetzen.
Die Caesar Cipher (Caesar-Verschlüsselung) ist eine der bekanntesten einfachen Verschlüsselungen: Jeder Buchstabe wird um eine feste Anzahl Stellen im Alphabet verschoben.
Das Problem ist so beliebt, weil es klein wirkt, aber viele typische Programmierfragen mitbringt: Wie arbeite ich sicher mit Zeichen? Wie mache ich ein sauberes wrap-around von Z zu A? Und wie gehe ich mit Groß-/Kleinschreibung um?
In diesem Artikel lernst du die Grundidee, baust zwei Java-Lösungen (eine straightforward, eine etwas idiomatischer), und bekommst nebenbei ein gutes Gefühl für ASCII/Unicode-Grundlagen in Java.
Problemstellung
Schreibe eine Funktion, die einen Text mit der Caesar Cipher verschlüsselt (und optional wieder entschlüsselt).
- Nur Buchstaben A-Z und a-z werden verschoben.
- Andere Zeichen (Leerzeichen, Punkte, Ziffern, etc.) bleiben unverändert.
- Die Verschiebung
shiftkann auch größer als26sein und soll korrekt funktionieren. - Groß- und Kleinschreibung sollen erhalten bleiben.
Mini-Beispiel:
- Eingabe:
"ABC",shift = 3=> Ausgabe:"DEF" - Eingabe:
"xyz",shift = 3=> Ausgabe:"abc" - Eingabe:
"Hello, World!",shift = 5=> Ausgabe:"Mjqqt, Btwqi!"
Die Idee dahinter
Das Kernkonzept ist Zeichenmanipulation: Wir betrachten einen Buchstaben als Zahl (Codepoint) und rechnen darauf.
Bei lateinischen Buchstaben kannst du mit Bereichen arbeiten:
'A'..'Z'sind zusammenhängend (Großbuchstaben)'a'..'z'sind zusammenhängend (Kleinbuchstaben)
Der Trick ist das wrap-around: Nach 'Z' soll wieder 'A' kommen. Das erreichst du mit Modulo-Rechnung auf einer 0-basierten Position im Alphabet.
Kurze Visualisierung als Liste:
'C'hat Alphabet-Index2(wenn'A'Index0ist)- Index
2+3=5 - Index
5entspricht'F' - Bei
'Z': Index25+3=28,28 % 26 = 2=>'C'
Schritt für Schritt zur Lösung
-
Shift normalisieren:
Reduziereshiftauf den Bereich0..25mitshift % 26. -
Zeichen durchlaufen:
Iteriere über alle Zeichen des Eingabestrings. -
Prüfen, ob Buchstabe:
Wenn'A'..'Z'oder'a'..'z', dann verschieben, sonst unverändert übernehmen. -
0-basigen Index berechnen:
index = ch - basemitbaseals'A'oder'a'. -
Verschieben und wrappen:
newIndex = (index + shift) % 26. -
Neues Zeichen bauen:
newChar = (char)(base + newIndex). -
Ergebnis zusammensetzen:
Nutze z.B.StringBuilderfür effizientes Zusammenbauen.
Java-Implementierung (Variante A: straightforward)
public class CaesarCipherStraightforward {
public static void main(String[] args) {
String text = "Hello, World!";
int shift = 5;
String encrypted = encrypt(text, shift);
String decrypted = decrypt(encrypted, shift);
System.out.println("Input: " + text);
System.out.println("Encrypted: " + encrypted);
System.out.println("Decrypted: " + decrypted);
}
public static String encrypt(String input, int shift) {
int normalizedShift = normalizeShift(shift);
StringBuilder result = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
if (ch >= 'A' && ch <= 'Z') {
result.append(shiftUppercase(ch, normalizedShift));
} else if (ch >= 'a' && ch <= 'z') {
result.append(shiftLowercase(ch, normalizedShift));
} else {
result.append(ch);
}
}
return result.toString();
}
public static String decrypt(String input, int shift) {
int normalizedShift = normalizeShift(shift);
int reverseShift = (26 - normalizedShift) % 26;
return encrypt(input, reverseShift);
}
private static int normalizeShift(int shift) {
int mod = shift % 26;
return mod < 0 ? mod + 26 : mod;
}
private static char shiftUppercase(char ch, int shift) {
int index = ch - 'A';
int newIndex = (index + shift) % 26;
return (char) ('A' + newIndex);
}
private static char shiftLowercase(char ch, int shift) {
int index = ch - 'a';
int newIndex = (index + shift) % 26;
return (char) ('a' + newIndex);
}
}
Diese Variante ist bewusst direkt: du siehst klar die zwei Fälle für A-Z und a-z. Wichtig ist die Methode normalizeShift, damit auch shift wie 31 oder -1 sauber funktionieren. Die Entschlüsselung macht keinen eigenen Code, sondern nutzt den Rückweg-Shift und ruft wieder encrypt auf.
Java-Implementierung (Variante B: Alternative oder Verbesserung)
public class CaesarCipherIdiomatic {
public static void main(String[] args) {
demo("ABC xyz", 3);
demo("Mjqqt, Btwqi!", -5);
demo("Attack at dawn.", 13);
}
private static void demo(String text, int shift) {
String out = caesar(text, shift);
System.out.println("Input: " + text + " | shift=" + shift);
System.out.println("Output: " + out);
System.out.println();
}
public static String caesar(String input, int shift) {
int normalizedShift = normalizeShift(shift);
return input.chars()
.map(c -> shiftIfLetter((char) c, normalizedShift))
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
}
private static int shiftIfLetter(char ch, int shift) {
if (isUppercaseAsciiLetter(ch)) {
return shiftInRange(ch, 'A', shift);
}
if (isLowercaseAsciiLetter(ch)) {
return shiftInRange(ch, 'a', shift);
}
return ch;
}
private static int shiftInRange(char ch, char base, int shift) {
int index = ch - base;
int newIndex = (index + shift) % 26;
return base + newIndex;
}
private static boolean isUppercaseAsciiLetter(char ch) {
return ch >= 'A' && ch <= 'Z';
}
private static boolean isLowercaseAsciiLetter(char ch) {
return ch >= 'a' && ch <= 'z';
}
private static int normalizeShift(int shift) {
int mod = shift % 26;
return mod < 0 ? mod + 26 : mod;
}
}
Diese Variante zeigt eine Alternative mit input.chars() und einem Collector in einen StringBuilder. Der Kern ist in kleine Methoden zerlegt: shiftIfLetter entscheidet, ob verschoben wird, und shiftInRange macht die eigentliche Mathematik für beide Alphabete gleich. Das ist oft leichter zu testen und reduziert Copy-Paste.
Beispiel: Eingabe und Ausgabe
Beispiel 1: Eingabe "ABC" mit shift=3 ergibt "DEF".
Beispiel 2: Eingabe "xyz" mit shift=3 ergibt "abc".
Beispiel 3: Eingabe "Hello, World!" mit shift=5 ergibt "Mjqqt, Btwqi!".
Typische Fehler
-
Shift nicht normalisieren:
Beishift=31erwartest du wie beishift=5, ohne% 26kommt Unsinn raus. -
Negativer Shift falsch behandelt:
In Java kann-1 % 26negativ sein; ohne Korrektur bekommst du falsches Wrapping. -
Wrap-around vergessen:
'Z'plus1darf nicht zu'['werden, sondern muss'A'werden. -
Gross/Klein vermischt:
Wenn du nur gegen'A'rechnest, zerstörst du Kleinbuchstaben. -
Nicht-Buchstaben versehentlich verschieben:
Satzzeichen und Leerzeichen sollen meist unverändert bleiben. -
Off-by-one bei Bereichschecks:
<statt<=kann'Z'oder'z'ausschliessen. -
String-Konkatenation in Schleife:
result = result + chin jeder Iteration wird langsam; nutzeStringBuilder.
Fazit
Mit der Caesar Cipher hast du ein kompaktes Problem gelöst, das sehr gut zum Üben von Zeichenmanipulation taugt: Bereiche prüfen, Indizes berechnen, und sauber wrappen.
Du hast außerdem gesehen, wie Java intern mit Zeichen arbeitet (Codepoints/Ints) und warum StringBuilder fast immer die bessere Wahl ist, wenn du viele Zeichen zusammensetzt.
Die hier gezeigten Lösungen arbeiten bewusst nur mit A-Z und a-z. Wenn du später Umlaute oder andere Alphabete unterstützen willst, wird es spannender: Dann reichen ASCII-Bereiche nicht mehr, und du musst mehr über Unicode, Normalisierung und Sprachregeln nachdenken.
