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
^ ^ ^ ^ ^ ^^
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
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
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
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
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')
The expected answer is:
7
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)