DEV Community

Cover image for Hamming (coding challenge) Part I
Tunde Oretade
Tunde Oretade

Posted on

Hamming (coding challenge) Part I

Hi Folks!! it's been a while since I wrote an article on what I have been doing lately. Currently I'm job searching, and in a bid to keep myself up to date, I decided that I will be solving coding challenges on the Exercism website daily using Ruby and also refactoring the solved code for readability.

For every challenge solved, I will be publishing my initial code iteration and the fully optimised version. I will also be describing lessons learnt while solving the coding challenge.
So hold on to your seats and enjoy the ride!

For starters, this article's focus will be on the Hamming coding challenge. According to Exercism, the anticipated lessons to be learnt are Advanced Enumeration, Loops,Conditionals, and Raising Exceptions. However I'm sure I learned more after I was done refactoring the initial solution.

For the Hamming challenge here is an abridged version of the instruction introducing the challenge:

Calculate the Hamming Distance between two DNA strands.
DNA strands are read using the letters C,A,G and T and Two
strands might look like this:

   GAGCCTACTAACGGGAT
   CATCGTAATGACGGCCT
   ^ ^ ^  ^ ^    ^^
Enter fullscreen mode Exit fullscreen mode

They have 7 differences, and therefore the Hamming Distance is 7.
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.

With the above instructions I set out to solve the challenge.
My initial solution produced the below code block:

class UnequalStrandError < ArgumentError
  def initialize(error_message = 'Strands must be of equal length')
    super
  end
end

class Hamming
  def self.compare_strands(first_strand, second_strand)
    first_array = first_strand.split('')
    second_array = second_strand.split('')
    first_array.each_with_object(container = []).with_index do |(item), index|
      second_array.at(index) != item and container << item or nil
    end
  end
  def self.check_strand_size(first_strand, second_strand)
    raise UnequalStrandError if first_strand.empty? && second_strand.size.positive?
    raise UnequalStrandError if first_strand.size.positive? && second_strand.empty?
    raise UnequalStrandError if first_strand.size > second_strand.size
    raise UnequalStrandError if first_strand.size < second_strand.size
  end
  def self.empty_strands(first_strand, second_strand)
    check_strand_size(first_strand, second_strand)
    return 0 if first_strand.empty? && second_strand.empty?
  end
  def self.compute(first_strand, second_strand)
    empty_strands(first_strand, second_strand)
    if first_strand.length == 1 && second_strand.length == 1
      first_strand != second_strand and 1 or 0
    else
      compare_strands(first_strand, second_strand).length
    end
  end
end

Enter fullscreen mode Exit fullscreen mode

Well, the above code block got everything working, however we all know there are ways we can optimise this code block. Before I go into refactoring the code, would it not be okay if I explain what I did?

let's start with class UnequalStrandError:

class UnequalStrandError < ArgumentError
  def initialize(error_message = 'Strands must be of equal length')
    super
  end
end
Enter fullscreen mode Exit fullscreen mode

The UnequalStrandError class is inheriting from the ArgumentError class, and I am initialising the error_message as 'Strands must be of equal length'. This is the message that will be displayed anytime the strands to be compared are of unequal size.

The UnequalStrandError class is also being raised in class Hamming see self.check_strand_size:

class Hamming

  def self.compare_strands(first_strand, second_strand)
    first_array = first_strand.split('')
    second_array = second_strand.split('')
    first_array.each_with_object(container = []).with_index do |(item), index|
      second_array.at(index) != item and container << item or nil
    end
  end

  def self.check_strand_size(first_strand, second_strand)
    raise UnequalStrandError if first_strand.empty? && second_strand.size.positive?
    raise UnequalStrandError if first_strand.size.positive? && second_strand.empty?
    raise UnequalStrandError if first_strand.size > second_strand.size
    raise UnequalStrandError if first_strand.size < second_strand.size
  end

  def self.empty_strands(first_strand, second_strand)
    check_strand_size(first_strand, second_strand)
    return 0 if first_strand.empty? && second_strand.empty?
  end

  def self.compute(first_strand, second_strand)
    empty_strands(first_strand, second_strand)
    if first_strand.length == 1 && second_strand.length == 1
      first_strand != second_strand and 1 or 0
    else
      compare_strands(first_strand, second_strand).length
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Now to explain what is happening inside class Hamming. This class was setup to have class methods. Class methods (in the context of the Hamming class) do not require that a Hamming object is created before using any method in class Hamming. That is why you can see the self prefix before any method.

The def self.check_strand_size(first_strand, second_strand) raises the UnequalStrandError if one of arguments are empty? and if the other is not empty. it also raises error if the argument sizes are not the same.

The def self.empty_strands(first_strand, second_strand) returns 0 if first_strand.empty? && second_strand.empty? and also calls def self.check_strand_size

def self.compare_strands(first_strand, second_strand) is spliting the strings into an array. Using the first array produced, I am comparing if each item iterated on is not the same as each item in the second array produced:

first_array.each_with_object(container = []).with_index do |(item), index|
      second_array.at(index) != item and container << item
    end
Enter fullscreen mode Exit fullscreen mode

if it is not the same, then the item is pushed and saved into another array called container

The last method is def self.compute(first_strand, second_strand). This method calls other methods I have defined earlier on like empty_strands(first_strand, second_strand) and also compare_strands(first_strand, second_strand). Since the answer required for the task is a number/integer, and I know that compare_strands returns an array if non-matching items, all I did was to get the size of the array and that is what produces the answer for the task.

So for example if I supply the following arguments to Hamming's compute method like so:

Hamming.compute('GAGCCTACTAACGGGAT', 'CATCGTAATGACGGCCT')
Enter fullscreen mode Exit fullscreen mode

The expected answer is:

7
Enter fullscreen mode Exit fullscreen mode

This code works like it is suppose to but there is still need to refactor the code for readability, and I will be doing this in Part II of this article. From the looks of things it seems I was able to match Exercism's expectations on what I will learn by solving this challenge. I used Advanced Enumeration, Loops,Conditionals, and also raised Exceptions. However more lessons will be learned in Part II of this article.

Top comments (0)