Search code examples
rubycaesar-cipher

How to preserve case of characters when using Caesar Cipher


I have a Caesar Cipher script in Ruby that is working but it returns the string as all upper-case letters instead of preserving the cases of the original string.

I could use capitalize to make it look good enough but I would like a more concrete way of preserving the cases.

Here is the script:

BASE_ORD = 'A'.ord

def caesar_cipher(phrase, key)
  cipher = phrase.gsub(/[a-z]/i) do |c|
    orig_pos = c.upcase.ord - BASE_ORD
    new_pos = (orig_pos + key) % 26
    (new_pos + BASE_ORD).chr
  end
  puts cipher
end

caesar_cipher("What a string!", 5) 

Any help or insight would be appreciated.


Solution

  • The simplest solution, given your existing code, is to check whether the character is uppercase or lowercase and set base_ord accordingly. Since the lowercase letters come after the uppercase letters in UTF-8 (as in ASCII), we can just test letter >= 'a', e.g.:

    base_ord = (letter >= 'a' ? 'a' : 'A').ord
    

    Here's the whole method with this change (you no longer need the BASE_ORD constant):

    def caesar_cipher(phrase, key)
      phrase.gsub(/[a-z]/i) do |letter|
        base_ord = (letter >= 'a' ? 'a' : 'A').ord
        orig_pos = letter.ord - base_ord
        new_pos = (orig_pos + key) % 26
        (new_pos + base_ord).chr
      end
    end
    
    puts caesar_cipher("What a string!", 5) # => Bmfy f xywnsl!
    

    Edit

    Amadan makes a good point about using String#tr. Here's a somewhat more concise implementation:

    ALPHABET = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
    # Or if you want to be fancy: ALPHABET = (?a..?z).flat_map {|c| [ c, c.upcase ] }.join
    
    def caesar_cipher(phrase, key)
      to_alphabet = ALPHABET.dup
      to_alphabet << to_alphabet.slice!(0, key * 2)
      phrase.tr(ALPHABET, to_alphabet)
    end
    
    puts caesar_cipher("What a string!", 5) # => Bmfy f xywnsl!