I am a Ruby beginner and i am working on a cypher program. It takes a phrase , transforms the string to numbers , increments the numbers with a given value then transforms them again in to a string. I would like to know how can i can keep the non letter symbols unchanged. like the " ! " or the space. The code i have wrote is bellow:
def caesar_cypher ( phrase, number=0)
letters = phrase.downcase.split(//)
letters_to_numbers= letters.map { |idx| idx.ord }
incrementor = letters_to_numbers.map { |idx| idx+number}
numbers_to_letters = incrementor.map { |idx| idx.chr}.join.capitalize
p numbers_to_letters
#binding.pry
end
caesar_cypher("Hello world!", 4)
caesar_cypher("What a string!", 6)
To iterate through all the characters of your phrase you can use String#each_char
. Then, while iterating, you can shift the letters (looping through the alphabet) and leave alone the other characters.
For coding the shift with looping, you'll need to take a look at the ASCII table; each character has a numeric value, and there are noticeable ranges:
from 0(48) to 9(57)
from A(65) to Z(90)
from a(97) to z(122)
Lets say that you want to loop through the lowercase letters; then adding 3
to 'y'
should give you a 'b'
. How do you code that? That would be (in C language):
('y' - 'a' + 3) % ('z' - 'a') + 'a'
Note: I'll stop with the explanations because there're a few things going on in the formula and I'm not sure about how to express them.
So here is an incomplete code for you:
def caesar_cypher ( string, shift = 0)
result = ""
string.each_char do |char|
case char
when 'a'..'z'
result << ((char.ord + shift - 'a'.ord) % 26 + 'a'.ord).chr
when 'A'..'Z'
# MISSING PART
when '0'..'9' # OOPS! ROMANS DIDN'T KNOW OF ZERO?
# MISSING PART
else
result << char
end
end
result
end
I hope this helps ;-)
Yes, you can pass characters through unmodified, but to do so you'll need to define what's a "letter" and what you want to include or exclude from the encoding within your #map block. Here's a slightly different way to approach the problem that does those things, but is also much shorter and adds some additional flexibility.
key % 26 == 0
, but allow it anyway. This helps with testing. Otherwise, you could simply raise an exception if you don't want to allow plaintext results, or set a default value for key .Using Ruby 3.0.2:
def caesar_cypher phrase, key
warn "no encoding when key=#{key}" if (key % 26).zero?
letters = [*(?A..?Z), *(?a..?z)]
encoding = letters.rotate(key).zip(letters).to_h.invert
phrase.chars.map { encoding.fetch _1, _1 }.join
end
You can verify that this gives you repeatable outputs with some of the following examples:
# verify your mapping with key=0,
# which returns the phrase as-is
caesar_cypher "foo bar", 0
#=> "foo bar"
caesar_cypher "foo bar", 5
#=> "ktt gfw"
caesar_cypher "Et tu, Brute?", 43
#=> "vk kl, silkV?"
# use any other rotation value you like;
# you aren't limited to just 0..51
caesar_cypher "Veni, vidi, vici", 152
#=> "Raje, reZe, reYe"
# UTF-8 and punctuation (actually, anything
# not /[A-Za-z]/) will simply pass through
# unencoded since it's not defined in the
# +encoding+ Hash
caesar_cypher "î.ô.ú.", 10
#=> "î.ô.ú."
The code above should work on most recent Ruby versions, but on versions older than 2.7 you may need to replace the _1
variables inside the block with something like:
phrase.chars.map { |char| encoding.fetch(char, char) }.join
instead of relying on numbered positional arguments. I can't think of anything else that would prevent this code from running on any Ruby version that's not past end-of-life, but if you find something specific please add a comment.
A_ORD = 'A'.ord
def caesar_cypher(str, offset)
h = ('A'..'Z').each_with_object(Hash.new(&:last)) do |ch,h|
h[ch] = (A_ORD + (ch.ord - A_ORD + offset) % 26).chr
h[ch.downcase] = (h[ch].ord + 32).chr
end
str.gsub(/./, h)
end
Try it.
caesar_cypher("Hello world!", 4)
#=> "Lipps asvph!"
caesar_cypher("What a string!", 6)
#=> "Cngz g yzxotm!"
In executing the first example the hash held by the variable h
equals
{"A"=>"E", "a"=>"e", "B"=>"F", "b"=>"f", "C"=>"G", "c"=>"g", "D"=>"H",
"d"=>"h", "E"=>"I", "e"=>"i", "F"=>"J", "f"=>"j", "G"=>"K", "g"=>"k",
"H"=>"L", "h"=>"l", "I"=>"M", "i"=>"m", "J"=>"N", "j"=>"n", "K"=>"O",
"k"=>"o", "L"=>"P", "l"=>"p", "M"=>"Q", "m"=>"q", "N"=>"R", "n"=>"r",
"O"=>"S", "o"=>"s", "P"=>"T", "p"=>"t", "Q"=>"U", "q"=>"u", "R"=>"V",
"r"=>"v", "S"=>"W", "s"=>"w", "T"=>"X", "t"=>"x", "U"=>"Y", "u"=>"y",
"V"=>"Z", "v"=>"z", "W"=>"A", "w"=>"a", "X"=>"B", "x"=>"b", "Y"=>"C",
"y"=>"c", "Z"=>"D", "z"=>"d"}
The snippet
Hash.new(&:last)
if the same as
Hash.new { |h,k| k }
where the block variable h
is the (initially-empty) hash that is being created and k
is a key. If a hash is defined
hash = Hash.new { |h,k| k }
then (possibly after adding key-value pairs) if hash
does not have a key k
, hash[k]
returns k
(that is, the character k
is left unchanged).
See the form of Hash::new that takes a block but no argument.
We can easily create a decrypting method.
def caesar_decrypt(str, offset)
caesar_cypher(str, 26-offset)
end
offset = 4
s = caesar_cypher("Hello world!", offset)
#=> "Lipps asvph!"
caesar_decrypt(s, offset)
#=> "Hello world!"
offset = 24
s = caesar_cypher("Hello world!", offset)
#=> Fcjjm umpjb!
caesar_decrypt(s, offset)
#=> "Hello world!"
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.