Home > Net >  Is it possible to keep the non letter symbols intact?
Is it possible to keep the non letter symbols intact?

Time:10-20

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)

CodePudding user response:

Solution Using Array#rotate and Hash#fetch

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.

  1. Create an Array of all uppercase and lowercase characters in the English alphabet, and assign each to a replacement value using the inverted hashed value of Array#rotate, where the rotation value is your reproducible cypher key.
  2. Warn when you won't have an encrypted value because the rotation is 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.
  3. Don't capitalize your sentences. That limits your randomness, and prevents you from having separate values for capital letters.
  4. Using a default value with Hash#fetch allows you to return any character that isn't in your Hash without encoding it, so UTF-8 or punctuation will simply be passed through as-is.
  5. Spaces are not part of the defined encoding in the Hash, so you can use String#join without having to treat them specially.

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
#=> "î.ô.ú."

Syntax Note for Numbered Arguments

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.

CodePudding user response:

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 ;-)

  • Related