Exercism - Rotational Cipher
This post shows you how to get Rotational Cipher exercise of Exercism.
Preparation
Before we click on our next exercise, let’s see what concepts of DART we need to consider

So we need to use the following concepts.
Classes
Classes define blueprints for objects. They can contain methods that work together to solve a problem.
class RotationalCipher {
String rotate({required String text, required int shiftKey}) {
// Rotate text by shiftKey positions
return text;
}
}
void main() {
RotationalCipher cipher = RotationalCipher();
String result = cipher.rotate(text: "hello", shiftKey: 13);
print(result); // "uryyb"
}
Required Named Parameters
Required named parameters must be provided when calling a function. They’re marked with the required keyword and make function calls more explicit.
void main() {
String rotate({required String text, required int shiftKey}) {
return text;
}
// Must provide both parameters
String result = rotate(text: "hello", shiftKey: 13);
// Error: missing required parameter
// String result2 = rotate(text: "hello");
}
Modulo Operator (%)
The modulo operator (%) returns the remainder after division. It’s essential for wrapping character shifts within the alphabet range (0-25).
void main() {
// Normalize shift key to 0-25 range
int shiftKey = 27;
int shift = shiftKey % 26;
print(shift); // 1 (27 % 26 = 1)
// Shift within alphabet
int code = 97; // 'a'
int shifted = ((code - 97 + 13) % 26) + 97;
print(shifted); // 110 ('n')
// ROT26 is same as ROT0
int shift26 = 26 % 26;
print(shift26); // 0
}
StringBuffer
StringBuffer is an efficient way to build strings by appending characters or strings. It’s more efficient than string concatenation in loops.
void main() {
// Create StringBuffer
final result = StringBuffer();
// Append characters
result.writeCharCode(97); // 'a'
result.writeCharCode(98); // 'b'
result.write('c'); // 'c'
// Convert to string
String text = result.toString();
print(text); // "abc"
// Use in loops
final buffer = StringBuffer();
for (int i = 0; i < 5; i++) {
buffer.write('$i');
}
print(buffer.toString()); // "01234"
}
Character Code Arithmetic
Characters have numeric codes (Unicode values). You can convert between characters and their codes, and perform arithmetic to shift letters.
void main() {
// Get character code
int codeA = 'A'.codeUnitAt(0);
print(codeA); // 65
int codea = 'a'.codeUnitAt(0);
print(codea); // 97
// Convert code to character
String letterA = String.fromCharCode(65);
print(letterA); // 'A'
// Shift letter
int codeB = codeA + 1;
String letterB = String.fromCharCode(codeB);
print(letterB); // 'B'
// Shift within alphabet range
int code = 122; // 'z'
int shifted = ((code - 97 + 1) % 26) + 97;
String letter = String.fromCharCode(shifted);
print(letter); // 'a' (wraps around)
}
Character Code Ranges
ASCII/Unicode character codes have specific ranges for letters. Lowercase letters are 97-122 (a-z), uppercase letters are 65-90 (A-Z).
void main() {
// Lowercase range: 97-122 (a-z)
int codeA = 'a'.codeUnitAt(0); // 97
int codeZ = 'z'.codeUnitAt(0); // 122
// Uppercase range: 65-90 (A-Z)
int codeAUpper = 'A'.codeUnitAt(0); // 65
int codeZUpper = 'Z'.codeUnitAt(0); // 90
// Check if character is lowercase
int code = 'm'.codeUnitAt(0);
bool isLowercase = code >= 97 && code <= 122;
print(isLowercase); // true
// Check if character is uppercase
int codeUpper = 'M'.codeUnitAt(0);
bool isUppercase = codeUpper >= 65 && codeUpper <= 90;
print(isUppercase); // true
}
String Indexing
String indexing allows you to access individual characters by position. You can iterate through a string character by character.
void main() {
String text = "hello";
// Access character by index
String first = text[0];
print(first); // 'h'
// Get string length
int length = text.length;
print(length); // 5
// Iterate through characters
for (int i = 0; i < text.length; i++) {
String char = text[i];
print(char); // h, e, l, l, o
}
}
codeUnitAt() Method
The codeUnitAt() method returns the Unicode code unit (character code) at a specific index in a string.
void main() {
String text = "ABC";
// Get code unit at index 0
int codeA = text.codeUnitAt(0);
print(codeA); // 65 ('A')
// Get code unit at index 1
int codeB = text.codeUnitAt(1);
print(codeB); // 66 ('B')
// Use in loops
for (int i = 0; i < text.length; i++) {
int code = text.codeUnitAt(i);
print('${text[i]}: $code');
// A: 65
// B: 66
// C: 67
}
}
writeCharCode() Method
The writeCharCode() method appends a character to a StringBuffer using its Unicode code unit.
void main() {
final buffer = StringBuffer();
// Write character by code
buffer.writeCharCode(65); // 'A'
buffer.writeCharCode(66); // 'B'
buffer.writeCharCode(67); // 'C'
String result = buffer.toString();
print(result); // "ABC"
// Use in cipher
int shiftedCode = 110; // 'n'
buffer.writeCharCode(shiftedCode);
print(buffer.toString()); // "ABCn"
}
For Loops
For loops allow you to iterate through a sequence, processing each element. They’re essential for transforming each character in a string.
void main() {
String text = "hello";
// Iterate through string indices
for (int i = 0; i < text.length; i++) {
String char = text[i];
int code = char.codeUnitAt(0);
print('$char: $code');
// h: 104
// e: 101
// l: 108
// l: 108
// o: 111
}
}
Conditional Logic
Conditional logic (if, else if, else) is used to check character types and apply different transformations. It’s essential for handling letters vs non-letters.
void main() {
int code = 'M'.codeUnitAt(0);
// Check if lowercase
if (code >= 97 && code <= 122) {
print('Lowercase letter');
}
// Check if uppercase
else if (code >= 65 && code <= 90) {
print('Uppercase letter');
}
// Not a letter
else {
print('Not a letter');
}
}
Arithmetic Operations
Arithmetic operations like addition (+), subtraction (-), and modulo (%) are used to calculate shifted character codes within the alphabet range.
void main() {
int code = 'a'.codeUnitAt(0); // 97
int shift = 13;
// Shift within lowercase alphabet
// 1. Subtract base (97) to get position in alphabet (0-25)
// 2. Add shift
// 3. Modulo 26 to wrap around
// 4. Add base back to get final code
int shifted = ((code - 97 + shift) % 26) + 97;
print(shifted); // 110 ('n')
// Example: 'z' + 1 wraps to 'a'
int codeZ = 'z'.codeUnitAt(0); // 122
int shiftedZ = ((codeZ - 97 + 1) % 26) + 97;
print(shiftedZ); // 97 ('a')
}
Comparison Operators
Comparison operators (>=, <=) are used to check if a character code falls within a specific range (lowercase or uppercase letters).
void main() {
int code = 'm'.codeUnitAt(0);
// Check if lowercase (97-122)
if (code >= 97 && code <= 122) {
print('Lowercase letter');
}
// Check if uppercase (65-90)
if (code >= 65 && code <= 90) {
print('Uppercase letter');
}
// Check if not a letter
if (code < 65 || (code > 90 && code < 97) || code > 122) {
print('Not a letter');
}
}
Introduction
Create an implementation of the rotational cipher, also sometimes called the Caesar cipher.
The Caesar cipher is a simple shift cipher that relies on transposing all the letters in the alphabet using an integer key between 0 and 26. Using a key of 0 or 26 will always yield the same output due to modular arithmetic. The letter is shifted for as many values as the value of the key.
The general notation for rotational ciphers is ROT +
A ROT13 on the Latin alphabet would be as follows:
Plain: abcdefghijklmnopqrstuvwxyz
Cipher: nopqrstuvwxyzabcdefghijklm
It is stronger than the Atbash cipher because it has 27 possible keys, and 25 usable keys.
Ciphertext is written out in the same formatting as the input including spaces and punctuation.
Examples
- ROT5 omg gives trl
- ROT0 c gives c
- ROT26 Cool gives Cool
- ROT13 The quick brown fox jumps over the lazy dog. gives Gur dhvpx oebja sbk whzcf bire gur ynml qbt.
- ROT13 Gur dhvpx oebja sbk whzcf bire gur ynml qbt. gives The quick brown fox jumps over the lazy dog.
How do we solve the rotational cipher?
To solve the rotational cipher:
- Normalize shift key: Use modulo 26 to ensure shift is in range 0-25
- Handle zero shift: If shift is 0, return original text (no change)
- Process each character:
- Get character code using
codeUnitAt() - Check if lowercase (97-122): shift within lowercase range
- Check if uppercase (65-90): shift within uppercase range
- Otherwise: keep character as-is (spaces, punctuation)
- Get character code using
- Shift formula:
((code - base + shift) % 26) + base- For lowercase: base = 97
- For uppercase: base = 65
- Build result: Use StringBuffer to efficiently build the result string
The key insight is that we shift letters within their case range, wrapping around using modulo arithmetic, while preserving all non-letter characters.
Solution
class RotationalCipher {
String rotate({required String text, required int shiftKey}) {
// Normalize the shift key to be within 0-25
final shift = shiftKey % 26;
// If shift is 0, return the original text
if (shift == 0) {
return text;
}
final result = StringBuffer();
for (int i = 0; i < text.length; i++) {
final char = text[i];
final code = char.codeUnitAt(0);
// Check if it's a lowercase letter (a-z: 97-122)
if (code >= 97 && code <= 122) {
// Shift within lowercase range
final shifted = ((code - 97 + shift) % 26) + 97;
result.writeCharCode(shifted);
}
// Check if it's an uppercase letter (A-Z: 65-90)
else if (code >= 65 && code <= 90) {
// Shift within uppercase range
final shifted = ((code - 65 + shift) % 26) + 65;
result.writeCharCode(shifted);
}
// Not a letter, keep it as is
else {
result.write(char);
}
}
return result.toString();
}
}
Let’s break down the solution:
-
class RotationalCipher- Main class:- Encapsulates the rotational cipher implementation
- Provides the
rotatemethod for encryption/decryption
-
String rotate({required String text, required int shiftKey})- Main method:- Takes text to encrypt/decrypt and shift key
- Returns rotated (encrypted/decrypted) text
- Uses required named parameters for clarity
-
final shift = shiftKey % 26- Normalize shift key:- Uses modulo 26 to ensure shift is in range 0-25
- ROT26 becomes ROT0 (same as no shift)
- ROT27 becomes ROT1, etc.
-
if (shift == 0) return text- Early return:- If shift is 0 (or 26, 52, etc.), no transformation needed
- Returns original text immediately for efficiency
-
final result = StringBuffer()- Create buffer:- StringBuffer for efficient string building
- More efficient than string concatenation in loops
-
for (int i = 0; i < text.length; i++)- Iterate through text:- Processes each character in the input string
- Uses index-based iteration
-
final char = text[i]- Get character:- Accesses character at current index
- Used for checking and writing non-letters
-
final code = char.codeUnitAt(0)- Get character code:- Gets Unicode code unit (ASCII value) of character
- Used to determine if letter and calculate shift
-
if (code >= 97 && code <= 122)- Check lowercase:- Lowercase letters have codes 97-122 (a-z)
- If true, character is a lowercase letter
-
final shifted = ((code - 97 + shift) % 26) + 97- Shift lowercase:- Subtracts 97 (base for ‘a’) to get position 0-25
- Adds shift amount
- Modulo 26 wraps around alphabet (z+1 → a)
- Adds 97 back to get final character code
- Example: ‘a’ (97) + 13 → ‘n’ (110)
-
result.writeCharCode(shifted)- Write shifted lowercase:- Appends shifted character to result buffer
- Uses character code to write character
-
else if (code >= 65 && code <= 90)- Check uppercase:- Uppercase letters have codes 65-90 (A-Z)
- If true, character is an uppercase letter
-
final shifted = ((code - 65 + shift) % 26) + 65- Shift uppercase:- Subtracts 65 (base for ‘A’) to get position 0-25
- Adds shift amount
- Modulo 26 wraps around alphabet (Z+1 → A)
- Adds 65 back to get final character code
- Example: ‘A’ (65) + 13 → ‘N’ (78)
-
result.writeCharCode(shifted)- Write shifted uppercase:- Appends shifted character to result buffer
- Preserves case of original letter
-
else result.write(char)- Keep non-letters:- For spaces, punctuation, numbers, etc.
- Writes character as-is (no transformation)
- Preserves formatting of original text
-
return result.toString()- Return result:- Converts StringBuffer to String
- Returns the fully transformed text
The solution efficiently encrypts/decrypts text by shifting letters within their case range while preserving all non-letter characters. The modulo arithmetic ensures the shift wraps around the alphabet correctly, and the early return for zero shift optimizes the common case. The StringBuffer provides efficient string building for the result.
A video tutorial for this exercise is coming soon! In the meantime, check out my YouTube channel for more Dart and Flutter tutorials. 😉
Visit My YouTube Channel