2
\$\begingroup\$

This question is part of a series solving the Rosalind challenges. For the previous question in this series, see The Genetic Code. The repository with all my up-to-date solutions so far can be found here.


Problem: HAMM

Given two strings \$s\$ and \$t\$ of equal length, the Hamming distance between \$s\$ and \$t\$, denoted \$d_H(s,t)\$, is the number of corresponding symbols that differ in \$s\$ and \$t\$.

Given:

Two DNA strings \$s\$ and \$t\$ of equal length (not exceeding 1 kbp).

Return:

The Hamming distance \$d_H(s,t)\$.

Sample Dataset:

GAGCCTACTAACGGGAT
CATCGTAATGACGGCCT

Sample Output:

7

My solution solves the sample dataset and the actual dataset given.

Dataset:

TGGGAGTTTAATGTCCCCATAAGGTGCCCGTATACGTGTAGTCGTAATCCGTACTCGTGGTCGTTGATTCGGGCGGATCCAGCTGCTGTGCCGGGTTCTCCCCTGGACTTGAGTGCTGAATAAGTTGCTGTCCCCGCTCACCGTCTAAGCCTGTCCGTTACAAATTGCTGGTCGACCATAACCCTGCGTACCGCCAGGAGCGTCGAGTCGCCCGGGAATTTCGGCGCAGAGGAATGGGATTGCTTCAAATATTATGTATCTGCCATTCAGTTTTGAGCGTCTTCGTGTCACTGTACCTGCGCCATGAAGGCTCAAATCACCCGCTCAGACAGGACCATCCCAAGTTACTGCACTTGTGGACGGGAGCGTCATCGGTTGGCATATATTGCCTCTATTGTCGTTGGGATGAAGTCCTAACCCTACCCTGTCTGGGACTCCTTTCCTGCCAGGGATTGAGCCATGATCCTTCTGTGGTCTATGTTTCGGTTACCATTATTGGCTTTGAGAGCGCCCCGGTACGGTACGCTTCCGGATCCGACCTTGGCATCATGAGTCGTGACCAACTGCTTTAAGCTTATAGGGGCCCATTAAGACGGCGCGGATCTTGTGTTGCGATGTTCTGTCGCAGGTGTATCAAGTACACTCGACGGCCCTGCTGGGGCTAATAACTCTACTGGCTATGGCGTAAGCTGGGCCGAGACGGCTCCTCGGAGCGGTCGCCCATGGATGAGACGAATGCAAACCTGTATATTCCACTCCACCCTAGAGGCCTTCTGGCCTGACTGTTACCTGAGACTGTGAACACCGATACAAGGCTGTGCTCATGACCAGTTGCCAGTTCTAAACAACATAGTCATCATGATTTGAGTCGCCAAGAGACTTGCTTTCCGAGACGGAATGAGCAAAGAGGCAGGCTTCGTCCTAAGTGGACCCGATTGCGTTTTACGAGGGGTGCCTTCGCGATGCGATTGTGAC
TACTCAATCCTCGTTACAACTAGGTGCCCGAAGTCAAGTAGCTCTGCCGCACGTCCGTGGTCATCGAGGGATGTGGATGCGGCAGCGGGGTCAGCTCGTCTCGTGCCAGTGTATATACGACTAAATACTGTCATCGCTCGCGGGCTGACCCTTTGCGTGGCACAGTGTTCCCCAAGCCTATCCATGCTCTCAGGGAGCAGCGTCTACCCGACCGCCGCTTATGGTGTGTAAGACTGCTTATGGTTCAAGACGGATCAAATGCACATTAATTTCGCAGCGTGATGATATTACACTACCTTCTGCATTGACGTTCAACTACACAAGTCGGAGCGGTCTATCCCATAGTGCAGTGCGCTTAGGTTGAAGCGTGAACGTTTATCAGTTATACCCTTCATTGTCCTCGAGGTGGTGTCATAAGGCTATGCTGCATGCCTCTCCGACCCTGCGAGATATTCTCCGTTTGTGCTTATGTATTCCCTGGTTCTCCTTAGTATAACCCTTTTTTCTGTGCTCCCCGACCTCGCAGTTCCGGCCAGTGCTTGCGTAACATGAGCAGTTACCTTAGTCATTACGCTTGGTAGTTCCCATTAGGAAGGCATGTATTTCGAGTTGCGGGATGGTTTGGGATGTGTGGCAAGATGTAAGGACCGCGTTGGTTACTCGATGAGACAAGCCAGGCACCGAACATCCTCGGGTCATCCGAATCATCCGAATGGTCGGCCACCGATCTGACGTGTGCCAACCCGTGGATGCCAGGCATCGTTAGAGGTATTTACCCTTGACTGTTATCTCGGAACGAGAACAACGTCAAATCCTTGCCCTTTTGATAACGGAGGACTACTATACTACCTAGTCATACCTATATCCGTCGCAAGGAATCTGAAATACCCGGACATAGAGAGAAAACAATAACACTGTGTCCGAAGAGGACTGTCTGGCCTTTCATTATTGATAACCGCGCAATGGCAGTTGGAC

Output:

456

app/HAMM.rb:

require_relative '../lib/Validation' 

def HammingDistance(left, right)
  v = Validation.new()
  v.EqualSize(left, right)
  distance = 0
  for index in 0..left.length - 1
    distance += 1 unless left[index] == right[index]
  end
  distance  
end

first = gets.chomp
second = gets.chomp
puts HammingDistance(first, second)

Yes, I've re-arranged my project files. There are 4 folders:

  • app

All applications, the 'end' solutions.

  • bnc

Benchmarks.

  • lib

Library with functions for applications.

  • tst

Tests for the library, later possibly for the applications as well.

The given code makes use of one library file which is tested in two different ways.

Library:

lib/Validation.rb

class Validation
  def Numeric(input)
    raise ArgumentError, 'Argument is not numeric' unless input.is_a?(Numeric)
  end
  def Uppercase(input)
    raise ArgumentError, 'Argument is not uppercase' unless input == input.upcase
  end
  def EqualSize(left, right)
    raise ArgumentError, 'Arguments are not of equal size' unless left.size == right.size
  end
end

Tests:

tst/Validation_tests.rb

Dir["../lib/*.rb"].each {|file| require file }

arr1 = [0, 1, 2, 3]
arr2 = [1, 2, 3, 4]
upp = "UPPER"
low = "LOWER"
num = 42
nums = "42"

n = Validation.new
n.EqualSize(arr1, arr2)
n.EqualSize(upp, low)
n.Uppercase(upp)
n.Numeric(num)

tst/Validation_unittests.rb

require 'rspec'
require_relative '../lib/Validation'

v = Validation.new()

describe "Size verification with empty right" do
  it "raises" do
    expect { v.EqualSize([1], []) }.to raise_error(ArgumentError)
  end
end

describe "Size verification with left right" do
  it "raises" do
    expect { v.EqualSize([], [1]) }.to raise_error(ArgumentError)
  end
end

describe "Size verification with both empty" do
  it "succeeds" do
    expect { v.EqualSize([], []) }.not_to raise_error
  end
end

describe "Size verification with size 1, different values" do
  it "succeeds" do
    expect { v.EqualSize([1], [0]) }.not_to raise_error
  end
end

describe "Size verification with both size 1, same values" do
  it "succeeds" do
    expect { v.EqualSize([1], [1]) }.not_to raise_error
  end
end

Basically, I can now calculate the Hamming distance while validating the input. The Hamming distance is important in bioinformatics because it allows us to count mutations in DNA and the like.

The validation library will likely grow in size, so I better get it done right from the start to avoid a lot of work later on.

With all this validating going on I'm more than moderately concerned about the naming of it all. Names should be clear, but not overly verbose. It's Ruby, not C#.

Re-usability and idiomaticness are also major concerns.

\$\endgroup\$
6
\$\begingroup\$

Generally, these programming challenges don't require you to do validation. How strict do you make the validation? Why not verify that the string contains nothing but ACTGU? In any case, your validation framework seems like overkill for a simple length check.

For the Hamming distance calculation itself, I suggest a more idiomatic one-liner

left.chars.zip(right.chars).count { |l, r| l != r }
\$\endgroup\$
  • \$\begingroup\$ Yes, my validation is overkill and not necessary. However, I'm trying to learn myself some general Ruby skills in the meantime. I had never written tests in Ruby before so I picked whatever I was implementing as a testing ground. Writing working code is easy, I use these challenges to learn writing decent code. \$\endgroup\$ – Mast Jun 5 '16 at 22:41
  • 1
    \$\begingroup\$ Nitpick: Some style guides discourage using l(letter el) as a variable name since, with many fonts, it's hard to tell it from a 1 (number one). \$\endgroup\$ – tokland Jun 6 '16 at 14:55
1
\$\begingroup\$

Adding another nitpick: "for" is kind of avoided in idiomatic usage. One suggestion would be to switch to a while or to a range .inject (though I'do as 200_success suggested, if I had the possibility to choose ;) )

I have much more doubts for what concerns the validation, or its purpose... is it something to validate anything that is coming to your mind? Why a class and not a module? And what happens for instance if I try to call Uppercase with value 42? Are there tests to cover that? My impression: as a single class it is already doing too much, it has no specific purpose (ok, it validates... what exactly?) and as a consequence it is already starting to suffer.

Think about maintaining this when you have hundreds of methods defined there...

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.