exercism

Exercism - Hamming

This post shows you how to get Hamming exercise of Exercism.

Stevinator Stevinator
7 min read
SHARE
exercism dart flutter hamming

Preparation

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

Hamming Exercise

So we need to use the following concepts.

List.generate

The List.generate method creates a list of a specified length by calling a function for each index. It’s perfect for creating lists based on calculations.

void main() {
  // Generate list based on index
  List<int> numbers = List.generate(5, (i) => i * 2);
  print(numbers); // [0, 2, 4, 6, 8]
  
  // Generate with conditional
  List<int> values = List.generate(3, (i) => i > 1 ? 1 : 0);
  print(values); // [0, 0, 1]
}

String Indexing

Strings in Dart can be accessed by index to get individual characters. You can compare characters at the same position in different strings.

void main() {
  String s1 = "ABC";
  String s2 = "ABD";
  
  // Access by index
  print(s1[0]); // 'A'
  print(s1[2]); // 'C'
  
  // Compare characters at same position
  bool different = s1[2] != s2[2];
  print(different); // true (C != D)
}

Comparison Operators

Comparison operators allow you to check if values are equal or different. The != operator checks for inequality.

void main() {
  char a = 'A';
  char b = 'B';
  
  // Equality
  bool equal = a == b;
  print(equal); // false
  
  // Inequality
  bool notEqual = a != b;
  print(notEqual); // true
}

Fold Method

The fold method is used to reduce a collection to a single value by iteratively combining each element with an existing value using a provided function.

void main() {
  List<int> numbers = [1, 2, 3, 4];
  
  // Sum all numbers
  int sum = numbers.fold(0, (total, number) => total + number);
  print(sum); // 10
  
  // Count with initial value
  int count = numbers.fold(0, (sum, val) => sum + val);
  print(count); // 10
}

ArgumentError

ArgumentError is used to signal that an argument passed to a function is invalid. It’s part of Dart’s exception system.

void main() {
  void validate(String s1, String s2) {
    if (s1.length != s2.length) {
      throw ArgumentError('strands must be of equal length');
    }
  }
  
  try {
    validate("ABC", "AB");
  } catch (e) {
    print(e); // ArgumentError: strands must be of equal length
  }
}

Conditional (Ternary) Operator

The conditional operator ? : is a shorthand for if-else statements. It’s useful for concise conditional expressions.

void main() {
  char a = 'A';
  char b = 'B';
  
  // Ternary operator
  int result = a != b ? 1 : 0;
  print(result); // 1 (they're different)
  
  // Used in list generation
  List<int> differences = List.generate(3, (i) => a != b ? 1 : 0);
  print(differences); // [1, 1, 1]
}

Introduction

Your body is made up of cells that contain DNA. Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime!

When cells divide, their DNA replicates too. Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. If we compare two strands of DNA and count the differences between them, we can see how many mistakes occurred. This is known as the “Hamming distance”.

The Hamming distance is useful in many areas of science, not just biology, so it’s a nice phrase to be familiar with :)

Instructions

Calculate the Hamming distance between two DNA strands.

We read DNA using the letters C, A, G and T. Two strands might look like this:

GAGCCTACTAACGGGAT
CATCGTAATGACGGCCT
^ ^ ^  ^ ^    ^^

They have 7 differences, and therefore the Hamming distance is 7.

Implementation notes

The Hamming distance is only defined for sequences of equal length, so an attempt to calculate it between sequences of different lengths should not work.

What is Hamming distance?

The Hamming distance between two strings of equal length is the number of positions at which the corresponding symbols are different. In other words, it measures the minimum number of substitutions required to change one string into the other, or the minimum number of errors that could have transformed one string into the other.

The Hamming distance is named after Richard Hamming, who introduced it in his fundamental paper on error-detecting and error-correcting codes. It’s widely used in information theory, coding theory, cryptography, and bioinformatics.

— Wikipedia

How can we calculate Hamming distance?

To calculate the Hamming distance between two DNA strands:

  1. Check that both strands have the same length (throw an error if not)
  2. Compare each character at the same position in both strands
  3. Count how many positions have different characters
  4. Return the count

For example, comparing “GAGCCTACTAACGGGAT” and “CATCGTAATGACGGCCT”:

  • Position 0: G != C → difference
  • Position 1: A == A → same
  • Position 2: G != T → difference
  • And so on…
  • Total: 7 differences

⚠️ Old Solution (No Longer Works)

Previously, the solution used asMap(), entries, and where with complex logic. Here’s what the old solution looked like:

class Hamming {
  int distance(String strandL, String strandR) {
    if(strandL.isEmpty ^ strandR.isEmpty) {
      throw ArgumentError("no strand must be empty");
    }
    
    if(strandL.length != strandR.length) {
      throw ArgumentError("left and right strands must be equal length");
    }
    
    return strandL.split('').asMap().entries.where((e) => e.value != strandR.split('')[e.key]).length;
  }
}

Why This Solution Doesn’t Work Anymore

The old solution has several issues:

  1. XOR operator for empty check: The ^ (XOR) operator checks if exactly one strand is empty, but this is unnecessary. The length check already handles this case. The XOR logic is confusing and doesn’t add value.

  2. Inefficient string splitting: The solution calls split('') twice - once for strandL and once for strandR inside the where clause. This is inefficient and creates unnecessary intermediate lists.

  3. Complex chaining: The combination of asMap(), entries, and where makes the code hard to read and understand.

  4. Redundant empty check: The empty check with XOR is redundant since empty strings have length 0, which would be caught by the length check anyway.

The exercise now requires:

  • Simpler, more efficient code using List.generate and direct string indexing
  • Using fold to sum differences instead of filtering and counting
  • Removing unnecessary empty string checks (length check is sufficient)
  • More readable code that’s easier to understand and maintain

Solution

class Hamming {
  int distance(String s1, String s2) {
    if (s1.length != s2.length) {
      throw ArgumentError('strands must be of equal length');
    }
    
    return List.generate(s1.length, (i) => s1[i] != s2[i] ? 1 : 0)
        .fold(0, (sum, val) => sum + val);
  }
}

Let’s break down the solution:

  1. if (s1.length != s2.length) - Validates that both strands have the same length:

    • If lengths differ, throws an ArgumentError with a descriptive message
    • This single check handles all edge cases including empty strings
  2. List.generate(s1.length, (i) => ...) - Creates a list with one element for each position:

    • s1.length determines how many elements to generate
    • For each index i, compares s1[i] with s2[i]
    • Uses ternary operator: if characters differ, returns 1; otherwise returns 0
    • This creates a list of 1s and 0s, where 1 represents a difference
  3. .fold(0, (sum, val) => sum + val) - Sums all the values in the list:

    • Starts with 0 as the initial value
    • For each value in the list, adds it to the running sum
    • The final sum is the total number of differences (Hamming distance)

The solution is more efficient, readable, and follows modern Dart best practices by using List.generate for iteration and fold for accumulation.


You can watch this tutorial on YouTube. So don’t forget to like and subscribe. 😉

Watch on YouTube
Stevinator

Stevinator

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