Coderbytes Letter Changes (Ruby)

944 Views Asked by At

Here is the assigned problem:

Using the Ruby language, have the function LetterChanges(str) take the str parameter being passed and modify it using the following algorithm. Replace every letter in the string with the letter following it in the alphabet (ie. c becomes d, z becomes a). Then capitalize every vowel in this new string (a, e, i, o, u) and finally return this modified string.*

I figured I'd attack this in two parts, however I cannot seem to figure out what I am doing wrong with the first half of the problem!

Here is my code as it currently stands.

def LetterChanges(str)

  str.downcase!
  str = str.split(//)
  alphabet_lower = ["a".."z"]

  i = 0
  letter = 0
  while i < str.length - 1
  if alphabet_lower[letter] == str[i]
    str[i] = alphabet_lower[letter + 1]
    i += 1
    letter = 0 ## Added this for the instance when letter > 0 after an 'else'
  else
    letter += 1
  end
  end

  str = str.join("")
  return str 

end

This code is having infinite loop. I did try a few other things such as

......
i = 0
alphabet.each do |letter|
if str[i] == letter
str[i] = letter.next ##I also tried letter + 1
i += 1
end
end
......
3

There are 3 best solutions below

1
On

Most probably, your if alphabet_lower[letter] == str[i] doesn't get a match, thus your letter increments to infinity, and you get an infinite loop. You can verify this by adding a puts on your else. It would help if you do as Yu Hao said and place your input and it's output. Also, kindly post the calling script for your LetterChanges.

As for the Socratic Way: The question left, is why does your if alphabet_lower[letter] == str[i] doesn't get a match? And how can you get around with it? :)

1
On
alphabet_lower = ["a".."z"]

This creates an Array of a single Range element, not what you expected. This is the reason alphabet_lower[letter] == str[i] is never true, causing infinite loop.

Change it to:

alphabet_lower = ("a".."z").to_a

Since there is also whitespace character in your string, it's better be:

alphabet_lower = ("a".."z").to_a + [' ']
1
On

@Yu has explained your problem. Here's another way that uses the form of String#gsub that uses a hash to substitute each character in the string.

All the work will be done by the following hash:

H = ('a'..'z').to_a.each_with_object(Hash.new { |h,k| k }) do |c,h|
  h[c] = case c
         when 'z' then 'A'
         when 'd', 'h', 'n', 't' then c.next.upcase
         else c.next
         end
end
  #=> {"a"=>"b", "b"=>"c", "c"=>"d", "d"=>"E", "e"=>"f", "f"=>"g", "g"=>"h",
  #    "h"=>"I", "i"=>"j", "j"=>"k", "k"=>"l", "l"=>"m", "m"=>"n", "n"=>"O",
  #    "o"=>"p", "p"=>"q", "q"=>"r", "r"=>"s", "s"=>"t", "t"=>"U", "u"=>"v",
  #    "v"=>"w", "w"=>"x", "x"=>"y", "y"=>"z", "z"=>"A"}

The construction of the hash is straightforward, except possibly the line that creates it, which is an argument of Enumerable#each_with_object:

Hash.new { |h,k| k }

This creates an empty hash with a default value. Specifically, if h does not have a key k, h[k] returns the default value k. We can see how that works:

H['a']      #=> "b" 
H['d']      #=> "E" 
H['%']      #=> "%" 
H['3']      #=> "3" 
H['zombie'] #=> "zombie" 

This allows us to write:

def convert(str)
  str.gsub(/./, H)
end

which produces the desired results:

convert 'bcd tuv'   #=> "cdE Uvw" 
convert 'bcd3?tuv'  #=> "cdE3?Uvw" 
convert '123D ghi$' #=> "123D hIj$" 

Note that if we created H without the default:

H = ('a'..'z').to_a.each_with_object({}) do |c,h|
  h[c] = case c
         ...
         end
end

the hash mapping would be the same, but we would obtain:

convert 'bcd tuv'   #=> "cdEUvw" 
convert 'bcd3?tuv'  #=> "cdEUvw" 
convert '123D ghi$' #=> "hIj" 

which is not what we want.