Stop a loop if a certain character is passed to it Ruby - 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.

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.

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

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

Related

Conditional statement not triggered

I wrote a piece of code that creates a random string based on an input. The user decides its length and whether it should contain numbers and special characters. I added a fail-safe routine because the end result is random:
def create
i = #number
while i != 0
# testing the script I have noticed that the if statement is (always) ignored.
if i == 2 && (#word_bank.include?(#special_chars) && #rand_ary.include?(#special_chars) == false)
#rand_ary << #special_chars[rand(0..#special_chars.size - 1)]
end
letter = rand(0..word_bank.size - 1)
#puts "#{i}, #{word_bank[letter]}"
#rand_ary << word_bank[letter]
word_bank.delete_at(letter)
i -= 1
end
#rand_string = #rand_ary.join()
puts #rand_string
#rand_string
end
If the user elects to include a special character, a counter runs from n - 0. When i = 2, and no character from a special character array is included, a random special character is manually included.
But this if-statement is never triggered. I can't figure out why.
If your variables have the kind of values I think they do, it's because include isn't the right method to use here.
If #word_bank and #rand_ary are arrays, include will check if any single element is equal to #special_chars. If #special_chars is itself an array, then it'll only return true if one of the elements in #word_bank/#rand_ary is an array.
['a', 'b', 'c', '!'].include?('!') # => true
['a', 'b', 'c', '!'].include?(['!']) # => false
['a', 'b', 'c', ['!']].include?(['!']) # => true
I think you're actually interested in whether there's any overlap between them. In that case, you can use the intersection (&) operator and check whether it's empty.
['a', 'b', 'c', '!'] & ['!'] # => ['!']
['a', 'b', 'c'] & ['!'] # => []

Advancing Vowel to the Next Vowel in Ruby

I'm working on beginner Ruby tutorials. I'm trying to write a method that will advance vowels to the next vowel. 'a' will become 'e', 'e' will become 'i', 'u' will become 'a', etc etc. I've been trying various ideas for quite a while, to no avail.
I think I'm on the right track in that I need to create an array of the vowels, and then use an index to advance them to the next array in the vowel. I just can't seem to create the right method to do so.
I know this isn't workable code, but my outline is along these lines. Where I run into issues is getting my code to recognize each vowel, and advance it to the next vowel:
def vowel_adv(str)
vowels = ["a", "e", "i", "o", "u"]
str = str.split('')
**str_new = str.map do |letter|
if str_new.include?(letter)
str_new = str_new[+1]
end**
# The ** section is what I know I need to find working code with, but keep hitting a wall.
str_new.join
end
Any help would be greatly appreciated.
Because we have only a few vowels, I would first define a hash containing vowel keys and vowel values:
vowels_hash = {
'a' => 'e',
'A' => 'E',
'e' => 'i',
'E' => 'I',
'i' => 'o',
'I' => 'O',
'o' => 'u',
'O' => 'U',
'u' => 'a',
'U' => 'A'
}
Then I would iterate over the alphabets present in each word / sentence like so:
word.split(//).map do |character|
vowels_hash[character] || character
end.join
Update:
BTW instead of splitting the word, you could also use gsub with regex + hash arguments like so:
word.gsub(/[aeiouAEIOU]/, vowels_hash)
Or like so if you want to be Mr. / Ms. Fancy Pants:
regex = /[#{vowels_hash.keys.join}]/
word.gsub(regex, vowels_hash)
Here's your code with the fewest corrections necessary to make it work:
def vowel_adv(str)
vowels = ["a", "e", "i", "o", "u"]
str = str.split('')
str_new = str.map do |char|
if vowels.include?(char)
vowels.rotate(1)[vowels.index(char)]
else
char
end
end
str_new.join
end
vowel_adv "aeiou"
=> "eioua"
Things that I changed include
addition of a block variable to the map block
returning the thing you're mapping to from the map block
include? is called on the Array, not on the possible element
finding the next vowel by looking in the array of vowels, not by incrementing the character, which is what I think you were trying to do.
Here's an improved version:
VOWELS = %w(a e i o u)
ROTATED_VOWELS = VOWELS.rotate 1
def vowel_adv(str)
str.
chars.
map do |char|
index = VOWELS.index char
if index
ROTATED_VOWELS[index]
else
char
end
end.
join
end
static Arrays in constants
nicer array-of-string syntax
String#chars instead of split
use the index to test for inclusion instead of include?
no assignment to parameters, which is a little confusing
no temporary variables, which some people like and some people don't but I've done here to show that it's possible
And, just because Ruby is fun, here's a different version which copies the string and modifies the copy:
VOWELS = %w(a e i o u)
ROTATED_VOWELS = VOWELS.rotate 1
def vowel_adv(str)
new_str = str.dup
new_str.each_char.with_index do |char, i|
index = VOWELS.index char
if index
new_str[i] = ROTATED_VOWELS[index]
end
end
new_str
end
The string class has a nice method for this. Demo:
p "Oh, just a test".tr("aeiouAEIOU", "uaeioUAEIO") # => "Ih, jost u tast"
To riff of of steenslag's answer a little.
VOWELS = %w{a e i o u A E I O U}
SHIFTED_VOWELS = VOWELS.rotate 1
def vowel_shifter input_string
input_string.tr!(VOWELS.join, SHIFTED_VOWELS.join)
end
and just for fun, consonants:
CONSONANTS = ('a'..'z').to_a + ('A'..'Z').to_a - VOWELS
SHIFTED_CONSONANTS = CONSONANTS.rotate 1
def consonant_shifter input_string
input_string.tr!(CONSONANTS.join, SHIFTED_CONSONANTS.join)
end

gsub - how to be efficient leet generator

for fun I am creating in ruby a simple leet (1337) generator
so i am doing something like this, which works but doesn't look very efficient, i am sure it can be accomplished with one line only...
def leet
words = words.gsub(/a/, '4')
words = words.gsub(/e/, '3')
words = words.gsub(/i/, '1')
words = words.gsub(/o/, '0')
words = words.gsub(/s/, '5')
words = words.gsub(/t/, '7')
puts words
end
Can you give me a help here? :) thanks!
def leet(word)
puts word.gsub(/[aeiost]/,'a'=>'4','e'=>'3','i'=>'1','o'=>'0','s'=>'5','t'=>'7')
end
def leet s
s.tr 'aeiost', '431057'
end
A more general version of megas's:
class Leet
##map = {
'a' => '4',
'e' => '3',
'i' => '1',
'o' => '0',
's' => '5',
't' => '7'
}
##re = Regexp.union(##map.keys)
def self.speak(str)
str.gsub(##re, ##map)
end
end
puts Leet.speak('leet')
# l337
Adjust ##map as needed and away you go.

How does defining [square bracket] method in Ruby work?

I am going through Programming Ruby - a pragmatic programmers guide and have stumbled on this piece of code:
class SongList
def [](key)
if key.kind_of?(Integer)
return #songs[key]
else
for i in 0...#songs.length
return #songs[i] if key == #songs[i].name
end
end
return nil
end
end
I do not understand how defining [ ] method works?
Why is the key outside the [ ], but when the method is called, it is inside [ ]?
Can key be without parenthesis?
I realize there are far better ways to write this, and know how to write my own method that works, but this [ ] method just baffles me... Any help is greatly appreciated, thanks
It's just syntactic sugar. There are certain syntax patterns that get translated into message sends. In particular
a + b
is the same as
a.+(b)
and the same applies to ==, !=, <, >, <=, >=, <=>, ===, &, |, *, /, -, %, **, >>, <<, !==, =~ and !~ as well.
Also,
!a
is the same as
a.!
and the same applies to ~.
Then,
+a
is the same as
a.+#
and the same applies to -.
Plus,
a.(b)
is the same as
a.call(b)
There is also special syntax for setters:
a.foo = b
is the same as
a.foo=(b)
And last but not least, there is special syntax for indexing:
a[b]
is the same as
a.[](b)
and
a[b] = c
is the same as
a.[]=(b, c)
Methods in ruby, unlike many languages can contain some special characters. One of which is the array lookup syntax.
If you were to implement your own hash class where when retrieving an item in your hash, you wanted to reverse it, you could do the following:
class SillyHash < Hash
def [](key)
super.reverse
end
end
You can prove this by calling a hash with the following:
a = {:foo => "bar"}
=> {:foo=>"bar"}
a.[](:foo)
=> "bar"
a.send(:[], :foo)
=> "bar"
So the def [] defined the method that is used when you do my_array["key"] Other methods that may look strange to you are:
class SillyHash < Hash
def [](key)
super.reverse
end
def []=(key, value)
#do something
end
def some_value=(value)
#do something
end
def is_valid?(value)
#some boolean expression
end
end
Just to clarify, the definition of a [] method is unrelated to arrays or hashes. Take the following (contrived) example:
class B
def []
"foo"
end
end
B.new[]
=> "foo"
the square brackets are the method name like Array#size you have Array#[] as a method and you can even use it like any other method:
array = [ 'a', 'b', 'c']
array.[](0) #=> 'a'
array.[] 1 #=> 'b'
array[2] #=> 'c'
the last one is something like syntactic sugar and does exactly the same as the first one. The Array#+ work similar:
array1 = [ 'a', 'b' ]
array2 = [ 'c', 'd' ]
array1.+(array2) #=> [ 'a', 'b', 'c', 'd' ]
array1.+ array2 #=> [ 'a', 'b', 'c', 'd' ]
array1 + array2 #=> [ 'a', 'b', 'c', 'd' ]
You can even add numbers like this:
1.+(1) #=> 2
1.+ 1 #=> 2
1 + 1 #=> 2
the same works with /, *, - and many more.
It's an operator overloader, it overrides or supplements the behavior of a method inside a class you have defined, or a class the behavior of which you are modifying. You can do it to other operators different from []. In this case you are modifying the behavior of [] when it is called on any instances of class SongList.
If you have songlist = SongList.new
and then you do songlist["foobar"]
then your custom def will come into operation and will assume that "foobar" is to be passed as the parameter (key) and it will do to "foobar" whatever the method says should be done to key.
Try
class Overruler
def [] (input)
if input.instance_of?(String)
puts "string"
else
puts "not string"
end
end
end
foo = Overruler.new
foo["bar"].inspect
foo[1].inspect

Creating an md5 hash of a number, string, array, or hash in Ruby

I need to create a signature string for a variable in Ruby, where the variable can be a number, a string, a hash, or an array. The hash values and array elements can also be any of these types.
This string will be used to compare the values in a database (Mongo, in this case).
My first thought was to create an MD5 hash of a JSON encoded value, like so: (body is the variable referred to above)
def createsig(body)
Digest::MD5.hexdigest(JSON.generate(body))
end
This nearly works, but JSON.generate does not encode the keys of a hash in the same order each time, so createsig({:a=>'a',:b=>'b'}) does not always equal createsig({:b=>'b',:a=>'a'}).
What is the best way to create a signature string to fit this need?
Note: For the detail oriented among us, I know that you can't JSON.generate() a number or a string. In these cases, I would just call MD5.hexdigest() directly.
I coding up the following pretty quickly and don't have time to really test it here at work, but it ought to do the job. Let me know if you find any issues with it and I'll take a look.
This should properly flatten out and sort the arrays and hashes, and you'd need to have to some pretty strange looking strings for there to be any collisions.
def createsig(body)
Digest::MD5.hexdigest( sigflat body )
end
def sigflat(body)
if body.class == Hash
arr = []
body.each do |key, value|
arr << "#{sigflat key}=>#{sigflat value}"
end
body = arr
end
if body.class == Array
str = ''
body.map! do |value|
sigflat value
end.sort!.each do |value|
str << value
end
end
if body.class != String
body = body.to_s << body.class.to_s
end
body
end
> sigflat({:a => {:b => 'b', :c => 'c'}, :d => 'd'}) == sigflat({:d => 'd', :a => {:c => 'c', :b => 'b'}})
=> true
If you could only get a string representation of body and not have the Ruby 1.8 hash come back with different orders from one time to the other, you could reliably hash that string representation. Let's get our hands dirty with some monkey patches:
require 'digest/md5'
class Object
def md5key
to_s
end
end
class Array
def md5key
map(&:md5key).join
end
end
class Hash
def md5key
sort.map(&:md5key).join
end
end
Now any object (of the types mentioned in the question) respond to md5key by returning a reliable key to use for creating a checksum, so:
def createsig(o)
Digest::MD5.hexdigest(o.md5key)
end
Example:
body = [
{
'bar' => [
345,
"baz",
],
'qux' => 7,
},
"foo",
123,
]
p body.md5key # => "bar345bazqux7foo123"
p createsig(body) # => "3a92036374de88118faf19483fe2572e"
Note: This hash representation does not encode the structure, only the concatenation of the values. Therefore ["a", "b", "c"] will hash the same as ["abc"].
Here's my solution. I walk the data structure and build up a list of pieces that get joined into a single string. In order to ensure that the class types seen affect the hash, I inject a single unicode character that encodes basic type information along the way. (For example, we want ["1", "2", "3"].objsum != [1,2,3].objsum)
I did this as a refinement on Object, it's easily ported to a monkey patch. To use it just require the file and run "using ObjSum".
module ObjSum
refine Object do
def objsum
parts = []
queue = [self]
while queue.size > 0
item = queue.shift
if item.kind_of?(Hash)
parts << "\\000"
item.keys.sort.each do |k|
queue << k
queue << item[k]
end
elsif item.kind_of?(Set)
parts << "\\001"
item.to_a.sort.each { |i| queue << i }
elsif item.kind_of?(Enumerable)
parts << "\\002"
item.each { |i| queue << i }
elsif item.kind_of?(Fixnum)
parts << "\\003"
parts << item.to_s
elsif item.kind_of?(Float)
parts << "\\004"
parts << item.to_s
else
parts << item.to_s
end
end
Digest::MD5.hexdigest(parts.join)
end
end
end
Just my 2 cents:
module Ext
module Hash
module InstanceMethods
# Return a string suitable for generating content signature.
# Signature image does not depend on order of keys.
#
# {:a => 1, :b => 2}.signature_image == {:b => 2, :a => 1}.signature_image # => true
# {{:a => 1, :b => 2} => 3}.signature_image == {{:b => 2, :a => 1} => 3}.signature_image # => true
# etc.
#
# NOTE: Signature images of identical content generated under different versions of Ruby are NOT GUARANTEED to be identical.
def signature_image
# Store normalized key-value pairs here.
ar = []
each do |k, v|
ar << [
k.is_a?(::Hash) ? k.signature_image : [k.class.to_s, k.inspect].join(":"),
v.is_a?(::Hash) ? v.signature_image : [v.class.to_s, v.inspect].join(":"),
]
end
ar.sort.inspect
end
end
end
end
class Hash #:nodoc:
include Ext::Hash::InstanceMethods
end
These days there is a formally defined method for canonicalizing JSON, for exactly this reason: https://datatracker.ietf.org/doc/html/draft-rundgren-json-canonicalization-scheme-16
There is a ruby implementation here: https://github.com/dryruby/json-canonicalization
Depending on your needs, you could call ary.inspect or ary.to_yaml, even.

Resources