Reputation: 323
I'm self-learning Ruby, and one assignment is to make a Caesar cipher. Using #gsub, I've been able to change my letters to integers ('c' => 2), shift them, then change the new integers to strings (2 => "2").
I've hit a wall, and the Ruby documentation isn't helping. When I try to #gsub the strings back to letters ("2" => 'c') it only recognizes 0-9. Everything after that is just a concatenation of those numbers ("12" => 'bc' instead of => 'l').
Why does Ruby do this, and how can I fix it?
Thanks for your help guys.
code: (I know it's sloppy beginner's code; I will try to edit it after it passes)
def convert_to_integer
puts "What would you like to encode?"
words = gets.chomp
words = words.split("")
words.map { |words| words.gsub!(/[a-z]/, 'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5, 'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11, 'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17, 's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23, 'y' => 24, 'z' => 25)
}
integer = words.map! { |letter| letter.to_i }
return integer
end
def shift_left(integer, number = 0)
puts "How many letters (to the left) would you like to shift it?"
number = gets.to_i
integer.map! { |n| n - number }
return integer
end
def convert_to_letter(integer)
integer.map! { |integer| integer.to_s }
integer.map! { |n| n.gsub(/[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]/, '0' => 'a', '1' => 'b', '2' => 'c', '3' => 'd', '4' => 'e', '5' => 'f', '6' => 'g', '7' => 'h', '8' => 'i', '9' => 'j', '10' => 'k', '11' => 'l', '12' => 'm', '13' => 'n', '14' => 'o', '15' => 'p', '16' => 'q', '17' => 'r', '18' => 's', '19' => 't', '20' => 'u', '21' => 'v', '22' => 'w', '23' => 'x', '24' => 'y', '25' => 'z')
}
print integer
end
convert_to_letter(shift_left(convert_to_integer))
Upvotes: 1
Views: 827
Reputation: 35483
It's easier and faster to use lookups:
@letter_to_number = ('a'..'z').zip(0..25).to_h
@number_to_letter = (0..25).zip('a'..'z').to_h
def convert_to_integers(letters)
letters.map{|l| @letter_to_number[l]}
end
def convert_to_letters(numbers)
numbers.map{|n| @number_to_letter[n]}
end
There's also a shortcut that combines the lookups and combines the methods.
@convert = (('a'..'z').zip(0..25) + (0..25).zip('a'..'z')).to_h
def convert(objects)
objects.map{|o| @convert[o]}
end
Upvotes: 1
Reputation: 18597
That's not how regular expressions work. "12".gsub(/[12]/, '12' => 'm')
does not produce "m"
. That code says to find any occurrence of "1" or "2", and replace it according to the following rule: "12" gets replaced with "m", and, implicitly, anything else gets replaced with nothing. Both the "1" and the "2" are occurrences of "1" or "2", but neither of them are "12", so they both get replaced with nothing. Thus the above results in just the empty string.
In fact gsub
and regular expressions are not really ideal for this problem. You could just do this:
def char_to_int(char)
char.ord - 97
end
def int_to_char(int)
(int + 97).chr
end
def caesar(string, shift)
string.split(" ").map do |word|
word.split("").map do |letter|
int_to_char((char_to_int(letter) - shift) % 26)
end.join
end.join(" ")
end
Upvotes: 0
Reputation: 230481
You don't need to do a gsub
there. gsub
is normally used to replace parts of a bigger string. You want to replace the whole thing.
This should do the trick:
def convert_to_letter(integers)
replacements = {0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd', 4 => 'e',
5 => 'f', 6 => 'g', 7 => 'h', 8 => 'i', 9 => 'j', 10 => 'k',
11 => 'l', 12 => 'm', 13 => 'n', 14 => 'o', 15 => 'p', 16 => 'q',
17 => 'r', 18 => 's', 19 => 't', 20 => 'u', 21 => 'v', 22 => 'w',
23 => 'x', 24 => 'y', 25 => 'z'
}
integers.map{|x| replacements[x]}.join
end
Also, be careful with destructive operations (map!
here). You may run into undesired side-effects (for example, some arrays will change when you think they shouldn't).
Upvotes: 2