LBarry
LBarry

Reputation: 115

Stop a loop if a certain character is passed to it Ruby

So I am working on a small assignment to transcribe DNA strands to RNA strands. My current code looks like this:

class Complement
  def self.of_dna(str)
    dna_rna = { 'G' => 'C', 'C' => 'G', 'T' => 'A', 'A' => 'U' }
    rna = []
    str.scan(/[GCTA]/).each do |x|
      rna << dna_rna[x]
    end
    rna.join('')
  end
end

It works perfectly, except for in one situation. If a DNA strand is passed that is partially correct, for example ACGTXXXCTTAA, my method will translate the DNA to RNA and just leave out the X's, giving me a result of UGCAGAAUU rather than just "". How can I make it so the loop will fail and exit when it receives a letter that isn't DNA related?

EDIT: The test I am trying to get to pass looks like this:

def test_dna_correctly_handles_partially_invalid_input
  # skip
  assert_equal '', Complement.of_dna('ACGTXXXCTTAA')
end

I attempted @Holger Just's idea from below, and received this error:

 1) Error:
ComplementTest#test_dna_correctly_handles_completely_invalid_input:
ArgumentError: ArgumentError
    /Users/LukasBarry/exercism/ruby/rna-transcription/rna_transcription.rb:6:in `block in of_dna'
    /Users/LukasBarry/exercism/ruby/rna-transcription/rna_transcription.rb:5:in `each'
    /Users/LukasBarry/exercism/ruby/rna-transcription/rna_transcription.rb:5:in `of_dna'
    rna_transcription_test.rb:43:in `test_dna_correctly_handles_completely_invalid_input'

The usual failure I've been getting from the above method is this:

1) Failure:
ComplementTest#test_dna_correctly_handles_partially_invalid_input [rna_transcription_test.rb:48]:
Expected: ""
  Actual: "UGCAGAAUU"

Any help would be greatly appreciated.

Upvotes: 1

Views: 94

Answers (3)

akuhn
akuhn

Reputation: 27793

Try this

class Complement
  def self.of_dna(str)
    return "" if str =~ /[^GCTA]/
    ...
  end
end

Fun fact, you don't even need a loop to replace characters

str = 'GATTACA'
str.tr('ATCG', 'UAGC')
# => 'CUAAUGU'

Is all you need.

Upvotes: 4

coreyward
coreyward

Reputation: 80051

I prefer using Hash#fetch for this because it'll raise a KeyError for you on mismatch, allowing you to write less code that validates inputs (i.e., less defensive programming), which I think is more valuable than cleverness (in which case I would recommend String#tr).

class DNA
  TranslationMap = { 'G' => 'C', 'C' => 'G', 'T' => 'A', 'A' => 'U' }
  attr_reader :dna

  def initialize(dna)
    @dna = dna
  end

  def to_rna
    dna.each_char.map do |nucleotide|
      TranslationMap.fetch(nucleotide)
    end.join('')
  rescue KeyError
    return false
  end
end

Feel free to adapt what happens when the error is rescued to fit your needs. I recommend raising a more specific exception (e.g. DNA::InvalidNucleotide) for the caller to handle.

In use:

dna = DNA.new 'GCTA'
 # => #<DNA:0x007fc49e903818 @dna="GCTA"> 
dna.to_rna
 # => "CGAU" 
dna = DNA.new 'ACGTXXXCTTAA'
 # => #<DNA:0x007fc49f064800 @dna="ACGTXXXCTTAA"> 
dna.to_rna
 # => false

Upvotes: 1

Holger Just
Holger Just

Reputation: 55803

You can also match X in your regex and perform some erorr handling if it is found in the string. This could look something like this:

class Complement
  def self.of_dna(str)
    dna_rna = { 'G' => 'C', 'C' => 'G', 'T' => 'A', 'A' => 'U' }
    rna = []
    str.scan(/[GCTAX]/).each do |x|
      return '' if x == 'X'
      rna << dna_rna[x]
    end
    rna.join('')
  end
end

Upvotes: 1

Related Questions