Increment alphabetical string in Ruby on rails

314 Views Asked by At

Task I want to solve:

Write a program that takes a string, will perform a transformation and return it. For each of the letters of the parameter string switch it by the next one in alphabetical order. 'z' becomes 'a' and 'Z' becomes 'A'. Case remains unaffected.

def rotone(param_1)
  a = ""
param_1.each_char do |x|
    if x.count("a-zA-Z") > 0
        a << x.succ
    else
        a << x
    end
end
   a
end

And I take this:

Input:  "AkjhZ zLKIJz , 23y "
Expected Return Value: "BlkiA aMLJKa , 23z "
Return Value:          "BlkiAA aaMLJKaa , 23z "

When iterators find 'z' or 'Z' it increment two times z -> aa or Z -> AA

2

There are 2 best solutions below

3
Rajagopalan On BEST ANSWER
input = "AkjhZ zLKIJz , 23y"

Code

p input.tr('a-yA-YzZ','b-zB-ZaA')

Output

"BlkiA aMLJKa , 23z"
0
Cary Swoveland On

Your problem is that String#succ (aka String#next) has been designed in a way that does not serve your purpose when the receiver is 'z' or 'Z':

'z'.succ #=> 'aa'
'Z'.succ #=> 'AA'

If you replaced a << x.succ with a << x.succ[0] you would obtain the desired result.


You might consider writing that as follows.

def rotone(param_1)
  param_1.gsub(/./m) { |c| c.match?(/[a-z]/i) ? c.succ[0] : c }
end

String#gsub's argument is a regular expression that matches every character (so every character is passed to gsub's block)1.

See also String#match?. The regular expression /[a-z]/i matches every character that is one of the characters in the character class [a-z]. The option i makes the match case-independent, so uppercase letters are matched as well.


Here is alternative way to write the method that employs two hashes that are defined as constants.

CODE = [*'a'..'z', *'A'..'Z'].each_with_object({}) do |c,h|
  h[c] = c.succ[0]
end.tap { |h| h.default_proc = proc { |_h,k| k } }
  #=> {"a"=>"b", "b"=>"c",..., "y"=>"z", "z"=>"a",
  #    "A"=>"B", "B"=>"C",..., "Y"=>"Z", "Z"=>"A"}
DECODE = CODE.invert.tap { |h| h.default_proc = proc { |_h,k| k } }
  #=> {"b"=>"a", "c"=>"b", ..., "z"=>"y", "a"=>"z",
  #    "B"=>"A", "C"=>"B", ..., "Z"=>"Y", "A"=>"Z"}

For example,

CODE['e'] #=> "f"
CODE['Z'] #=> "A"
CODE['?'] #=> "?"
DECODE['f'] #=> "e"
DECODE['A'] #=> "Z"
DECODE['?'] #=> "?"

Let's try using gsub, CODE and DECODE with an example string.

str = "The quick brown dog Zelda jumped over the lazy fox Arnie"

rts = str.gsub(/./m, CODE)
  #=> "Uif rvjdl cspxo eph Afmeb kvnqfe pwfs uif mbaz gpy Bsojf"
rts.gsub(/./m, DECODE)
  #=> "The quick brown dog Zelda jumped over the lazy fox Arnie"

See Hash#merge, Object#tap, Hash#default_proc=, Hash#invert and the form of Sting#gsub that takes a hash as its optional second argument.

Adding the default proc to the hash h causes h[k] to return k if h does not have a key k. Had CODE been defined without the default proc,

CODE = [*'a'..'z', *'A'..'Z'].each_with_object({}) { |c,h| h[c] = c.succ[0] }
  #=> {"a"=>"b", "b"=>"c",..., "y"=>"z", "z"=>"a",
  #    "A"=>"B", "B"=>"C",..., "Y"=>"Z", "Z"=>"A"}

gsub would skip over characters that are not letters:

rts = str.gsub(/./m, CODE)
  #=> "UifrvjdlcspxoephAfmebkvnqfepwfsuifmbazgpyBsojf"

Without the default proc we would have to write

rts = str.gsub(/./m) { |s| CODE.fetch(s, s) }
  #=> "Uif rvjdl cspxo eph Afmeb kvnqfe pwfs uif mbaz gpy Bsojf"

See Hash#fetch.

1. The regular expression /./ matches every character other than line terminators. Adding the option m (/./m) causes . to match line terminators as well.