exercism

Exercism - Atbash Cipher

This post shows you how to get Atbash Cipher exercise of Exercism.

Stevinator Stevinator
8 min read
SHARE
exercism dart flutter atbash-cipher

Preparation

Before we click on our next exercise, let’s see what concepts of DART we need to consider

Atbash Cipher Exercise

So we need to use the following concepts.

String Methods

Strings in Dart have many useful methods. The toLowerCase() method converts all characters to lowercase, split() divides a string into a list of substrings, and join() combines a list of strings into a single string.

void main() {
  String text = "HELLO WORLD";
  String lower = text.toLowerCase();
  List<String> chars = lower.split('');
  String joined = chars.join('');
  
  print(lower); // "hello world"
  print(chars); // ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
  print(joined); // "helloworld"
}

Regular Expressions (RegExp)

Regular expressions are patterns used to match character combinations in strings. The replaceAll() method can use RegExp to find and replace patterns.

void main() {
  String text = "Hello, World! 123";
  
  // Remove all non-alphanumeric characters
  String cleaned = text.replaceAll(RegExp(r'[^a-z0-9]', caseSensitive: false), '');
  print(cleaned); // "HelloWorld123"
  
  // Remove only punctuation
  String noPunct = text.replaceAll(RegExp(r'[^\w\s]'), '');
  print(noPunct); // "Hello World 123"
}

Map Method

The map method transforms each element of a collection into another element, creating a new collection with the transformed values.

void main() {
  List<int> numbers = [1, 2, 3, 4];
  List<int> doubled = numbers.map((n) => n * 2).toList();
  
  print(doubled); // [2, 4, 6, 8]
  
  List<String> words = ['hello', 'world'];
  List<String> upper = words.map((w) => w.toUpperCase()).toList();
  print(upper); // ['HELLO', 'WORLD']
}

Static Members

Static members (variables and methods) belong to the class itself rather than to instances of the class. They can be accessed without creating an instance.

class MathUtils {
  static const double pi = 3.14159;
  
  static int add(int a, int b) => a + b;
}

void main() {
  print(MathUtils.pi); // 3.14159
  print(MathUtils.add(5, 3)); // 8
}

String Indexing and Methods

Strings in Dart support indexing and methods like indexOf() to find the position of a character, contains() to check if a string contains a substring, and substring() to extract a portion of a string.

void main() {
  String alphabet = 'abcdefghijklmnopqrstuvwxyz';
  
  int index = alphabet.indexOf('c'); // 2
  bool hasC = alphabet.contains('c'); // true
  String char = alphabet[2]; // 'c'
  String reversed = alphabet.split('').reversed.join(''); // 'zyxwvutsrqponmlkjihgfedcba'
  
  // substring() extracts a portion of the string
  String text = "Hello World";
  String sub = text.substring(0, 5); // "Hello"
  String sub2 = text.substring(6); // "World"
  
  print(index);
  print(hasC);
  print(char);
  print(reversed);
  print(sub);
  print(sub2);
}

For Loops

For loops allow you to iterate over a range of values or through a collection. They’re useful for processing data in chunks or performing operations a specific number of times.

void main() {
  // Iterate with a counter
  for (int i = 0; i < 5; i++) {
    print(i); // 0, 1, 2, 3, 4
  }
  
  // Iterate in steps
  for (int i = 0; i < 10; i += 2) {
    print(i); // 0, 2, 4, 6, 8
  }
  
  // Process string in chunks
  String text = "abcdefghij";
  List<String> chunks = [];
  for (int i = 0; i < text.length; i += 3) {
    int end = i + 3 > text.length ? text.length : i + 3;
    chunks.add(text.substring(i, end));
  }
  print(chunks); // ['abc', 'def', 'ghi', 'j']
}

Introduction

Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.

The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards. The first letter is replaced with the last letter, the second with the second-last, and so on.

An Atbash cipher for the Latin alphabet would be as follows:

Plain: abcdefghijklmnopqrstuvwxyz

Cipher: zyxwvutsrqponmlkjihgfedcba

It is a very weak cipher because it only has one possible key, and it is a simple mono-alphabetic substitution cipher. However, this may not have been an issue in the cipher’s time.

Ciphertext is written out in groups of fixed length, the traditional group size being 5 letters, leaving numbers unchanged, and punctuation is excluded. This is to make it harder to guess things based on word boundaries. All text will be encoded as lowercase letters.

Examples

  • Encoding “test” gives “gvhg”
  • Encoding “x123 yes” gives “c123b vh”
  • Decoding “gvhg” gives “test”
  • Decoding “gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt” gives “thequickbrownfoxjumpsoverthelazydog”

What is the Atbash cipher?

Atbash is a monoalphabetic substitution cipher originally used to encrypt the Hebrew alphabet. It can be modified for use with any known writing system with a standard collating order. The name derives from the first, last, second, and second-to-last Hebrew letters (Aleph–Taw–Bet–Shin).

The Atbash cipher is a particular type of monoalphabetic cipher formed by taking the alphabet (or abjad, syllabary, etc.) and mapping it to its reverse, so that the first letter becomes the last letter, the second letter becomes the second to last letter, and so on.

— Wikipedia

How does the Atbash cipher work?

To encode or decode using Atbash:

  1. Convert the text to lowercase
  2. Remove all non-alphanumeric characters (keep only letters and numbers)
  3. For each letter, find its position in the alphabet (a=0, b=1, …, z=25)
  4. Replace it with the letter at the reverse position (z=0, y=1, …, a=25)
  5. Keep numbers unchanged
  6. For encoding, group the result into 5-character chunks separated by spaces

For example, encoding “test”:

  • Convert to lowercase: “test”
  • Remove non-alphanumeric: “test” (already clean)
  • Transform letters:
    • t (position 19) → g (position 6, which is 25-19)
    • e (position 4) → v (position 21, which is 25-4)
    • s (position 18) → h (position 7, which is 25-18)
    • t (position 19) → g (position 6, which is 25-19)
  • Result: “gvhg”

Solution

class AtbashCipher {
  static const String _alpha = 'abcdefghijklmnopqrstuvwxyz';

  static String _transform(String s) => s
      .toLowerCase()
      .replaceAll(RegExp(r'[^a-z0-9]'), '')
      .split('')
      .map((c) => _alpha.contains(c) ? _alpha[25 - _alpha.indexOf(c)] : c)
      .join();

  String encode(String input) {
    final t = _transform(input);
    if (t.length <= 5) return t;
    List<String> groups = [];
    for (int i = 0; i < t.length; i += 5) {
      groups.add(t.substring(i, i + 5 > t.length ? t.length : i + 5));
    }
    return groups.join(' ');
  }

  String decode(String input) => _transform(input);
}

Let’s break down the solution:

  1. static const String _alpha - Defines the alphabet as a constant that can be accessed without creating an instance
  2. _transform(String s) - A private static method that performs the core transformation:
    • toLowerCase() - Converts all characters to lowercase
    • replaceAll(RegExp(r'[^a-z0-9]'), '') - Removes all characters that are not lowercase letters or digits
    • split('') - Converts the string into a list of individual characters
    • map((c) => ...) - Transforms each character:
      • If it’s a letter, finds its position and replaces it with the letter at the reverse position (25 - index)
      • If it’s a number or other character, keeps it unchanged
    • join() - Combines the transformed characters back into a string
  3. encode(String input) - Encodes the input:
    • Calls _transform() to get the transformed string and stores it in t
    • If the result is 5 characters or less, returns it as-is
    • Otherwise, creates an empty list groups to store the chunks
    • Uses a for loop to iterate through the string in steps of 5:
      • Calculates the end index, ensuring it doesn’t exceed the string length
      • Extracts a substring of up to 5 characters using substring()
      • Adds each chunk to the groups list
    • Joins all groups with spaces and returns the result
  4. decode(String input) - Decodes the input by simply calling _transform() (the cipher is symmetric, so encoding and decoding use the same transformation)

The solution uses a straightforward for loop to group the encoded text into 5-character chunks, making it easy to read and understand.


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
Stevinator

Stevinator

Stevinator is a software engineer passionate about clean code and best practices. Loves sharing knowledge with the developer community.