DEV Community

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

Posted on

Hamming (coding challenge) Part II

Hamming coding challenge Part I

This article is the second part of Exercism's Hamming coding challenge. Initially I had explained my first solution and the lesson learnt as hinted by Exrecism. This second part focuses more on how I refactored my initial solution as seen in part I.

The refactored code goes like this:

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


class Hamming
  def self.compute(first_strand, second_strand)
    new(first_strand, second_strand).distance
  end

  attr_reader :distance, :first_strand, :second_strand

  private

  def initialize(first_strand, second_strand)
    @first_strand = first_strand
    @second_strand = second_strand
    validate
    @distance = nucleotides.count { |n1, n2| n1 != n2 }
  end

  def nucleotides(first_strand, second_strand)
    first_strand.chars.zip(second_strand.chars)
  end

  public

  def validate
    raise UnequalStrandError unless first_strand.length == second_strand.length
  end
end
Enter fullscreen mode Exit fullscreen mode

From this second iteration you can observed that it is now leaner and readable. However, what did do to make it leaner and readable?
As usual I will start with the UnequalStrandError class. In the new code I created a function called validate. This will take care of raising errors whenever any error is encountered in the code. Note that I now have a one line code:

 raise UnequalStrandError unless first_strand.length == second_strand.length
Enter fullscreen mode Exit fullscreen mode

compared to what I had earlier to help handle raising errors:

 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
Enter fullscreen mode Exit fullscreen mode

Secondly I refactored compute as :

  def self.compute(first_strand, second_strand)
    new(first_strand, second_strand).distance
  end
Enter fullscreen mode Exit fullscreen mode

The compute method in the above is a class method as indicated with the self prefix and also in compute body, I instatiated Hamming class and called distance on it.
The distance method is defined in initialize method:

  def initialize(first_strand, second_strand)
    @first_strand = first_strand
    @second_strand = second_strand
    validate
    **@distance = nucleotides.count { |n1, n2| n1 != n2 }**
  end
Enter fullscreen mode Exit fullscreen mode

and it is defined as an instance variable which is then exposed using attr_reader method.

  attr_reader :distance, :first_strand, :second_strand
Enter fullscreen mode Exit fullscreen mode

Note that @distance is a variable holding the result derived from nucleotides.count { |n1, n2| n1 != n2 }. Also the nucleotides method defined in the body of Hamming class handles the separation and zipping of first_strand and second_strand
In the initial code I had used the split() method to get an array from the string supplied but I found chars() to be effective as well. After creating the array I zipped both first_strand and second_strand array together. While using the zip() method this is what it does:

  first_strand = [G,A,G,C]
  second_strand = [C,A,T,C]
  first_strand.zip(second_strand)
  result = [[G,C],[A,A],[G,T],[C,C]]
Enter fullscreen mode Exit fullscreen mode

After zipping I called the count() method on the zipped array and this method was able to get a count of items in the sub array that are not the same.

With this refactoring for readability, I learnt about self, and what self means in the context of this challenge. I understood that when self is used in a code, it refers to the class and in this case it is reffering to the class Hamming.
I also learnt a new trick: instantiating a class in a class method and calling a defined method in the class on it.

  def self.compute(first_strand, second_strand)
    new(first_strand, second_strand).distance
  end
Enter fullscreen mode Exit fullscreen mode

When Hamming.compute is called, the new class instantiation will also call the method involved. This is a quick way of getting results instead of having to create new objects of class Hamming.
I also learned that, I can raise Errors in the initialize method to catch the errors thrown as quick as possible.

These are what I learned while solving the hamming coding challenge on Exercism. I will be solving more challenges on Exercism and do watch out for more articles on how I solved and refactored the challenges.

Top comments (0)