Ruby |Breaking down the Caesar Cipher

Caesar Cipher in Ruby

From Wikipedia:

In cryptography, a Caesar cipher, also known as Caesar’s cipher, the shift cipher, Caesar’s code or Caesar shift, is one of the simplest and most widely known encryption techniques. It is a type of substitution cipher in which each letter in the plaintext is replaced by a letter some fixed number of positions down the alphabet. For example, with a left shift of 3, D would be replaced by A, E would become B, and so on. The method is named after Julius Caesar, who used it in his private correspondence.

There’s a brief video about it from Harvard’s CS50 class.

Breaking down the following solution line by line:

1  puts "Word please: "
2  text = gets.chomp.downcase
3
4  puts "Number please: "
5  n = gets.chomp.to_i
6
7  def caesar_cipher(text, n)
8    alphabet = ('a'..'z').to_a 
9    key = Hash[alphabet.zip(alphabet.rotate(n))]
10   text.each_char.inject("") { |newtext, char| newtext + key[char] }
11 end
12
13 puts caesar_cipher(text, n)

Line 1:

  puts "Word please: "

Prompts the user to write a word.

Line 2:

  text = gets.chomp.downcase

gets takes the word the user writes and .chomp cuts off the end parameters (like “/n”). The new word is now referred to as ‘text’.

Line 4:

  puts "Number please: "

Prompts the user to write a number.

Line 5:

  n = gets.chomp.to_i

gets takes the input number (which is a string)

.chomp cuts the end parameters off, and then .to_i turns the number into an integer.

The new number is now referred to as ‘n’.

Line 7:

  def caesar_cipher(text, n)

def caesar_cipher(text, n) defines a new method called caesar_cipher that takes in two arguments text and n which are taken from Line 2 and Line 4 respectively.

Line 8:

  alphabet = ('a'..'z').to_a

Defines a new variable called alphabet that is equal to individual letters a all the way to z. These letters are each their own individual string. The .to_a turns the 26 letters into an array. This array is named alphabet.

Line 9:

  key = Hash[alphabet.zip(alphabet.rotate(n))]

A new variable key is defined. It is equal to Hash[alphabet.zip(alphabet.rotate(n))]

Let’s break it down.

alphabet.rotate(n) – What does this do?
.rotate is a method in Ruby that returns a new array by rotating the array elements.

For example:

       a = [ "a", "b", "c", "d" ]
       a.rotate #=> ["b", "c", "d", "a"]
       a #=> ["a", "b", "c", "d"]
       a.rotate(2) #=> ["c", "d", "a", "b"]
       a.rotate(-3) #=> ["b", "c", "d", "a"]

In this case, alphabet.rotate(n) takes the alphabet variable (the array of all 26 letters) and rotates the array n number of times. The n comes from what the user puts when prompted in the beginning (see Line 5).

alphabet.rotate(n) is inside alphabet.zip(). What does alphabet.zip() do?

.zip is a useful method for combining collections in an ordered way.

       [1,2,3].zip(['a', 'b', 'c'])
       #=> [[1, "a"], [2, "b"], [3, "c"]]

As you can see, the above two arrays are combined together so that each number is paired with each letter. The number 1 is associated with ‘a’ and so forth.

.zip can also take 2 (or more) arguments to zip into the first:

       ['a', 'b', 'c'].zip( [1,2,3], ['oogie', 'boogie', 'booger'] )
       #=> [["a", 1, "oogie"], ["b", 2, "boogie"], ["c", 3, "booger"]]

So going back to our code, alphabet.zip(alphabet.rotate(n)) is taking our rotated alphabet variable array and matching the new order with our original alphabet array. For example, if n was 2, our alphabet variable would match with the new rotated alphabet like so:

  n = 2
  alphabet = ['a', 'b', 'c', 'd', 'e'..'z']
  new_alphabet = ['c', 'd', 'e', 'f', 'g'..'b']  // new_alphabet = alphabet.rotate(2)
  alphabet.zip(new_alphabet) = ['a', 'c'],['b', 'd'], ['c', 'e'] ..['z', 'b']

Hash[alphabet.zip(alphabet.rotate(n))]
Hash turns each pairing into key => value pairings.

For example, alphabet.zip(new_alphabet) would be equal to [['a' => 'c'], ['b' => 'd'], ['c' => 'e']..['z' => 'b']]. So now the value for ‘a’ is ‘c’, and so on.

Line 10:

Let’s break down this, too:

  text.each_char.inject("") { |newtext, char| newtext + key[char] }

What exactly is this line of code doing??

  text

text takes the string entered by the user (See Line 2).

  text.each_char

each_char takes the string entered by the user (text) and separates the string into their separate characters. For example, if text were “apple”, each_char would make “apple” turn into “a”, “p”, “p”, “l”, “e”.

  text.each_char.inject("")

.inject("") sends each character through the block (the block being everything between the brackets {}).  Each character of text passes through the block in the argument place labeled char.

The ("") defines the default starting point. It means the first time we pass through the block, we are defaulting (starting) with an empty string (“”).

  { |newtext, char| newtext + key[char] }

This is a block. It is connected to the .inject enumerator method, which just means every time you use .inject, you will have a block following it. There are two arguments in this block ‘newtext‘ and ‘char‘ that are new.

newtext is the default (starting) string, which in the first go through, would be an empty string (“”) because that’s what we declared after the .inject method: text.each_char.inject("")

char is the individual characters from the text variable.

key[char] takes in the individual character [char] and assigns its value found from the key variable we defined in Line 9.

All the individual characters will be run through the block until there are no more characters in the text string. To see how this works, let’s use the example of “apple”.

     text.each_char.inject("") { |newtext, char| newtext + key[char] }

     text = "apple"
     n = "2"

     first run through => 
     "apple".each_char = "a", "p", "p", "l", "e"
     ("a", "p", "p", "l", "e").inject("") { |"", "a"| "" + key["a"] }
     key["a"] == "c"
1st  ("a", "p", "p", "l", "e").inject("") { |"", "a"| "" + "c" }
2nd  ("p", "p", "l", "e").inject("c") { |"c", "p"| "c" + [key["p"]=="r"] }
3rd  ("p", "l", "e").inject("r") { |"r", "p"| "cr" + "r" }
4th  ("l", "e").inject("r") { |"r", "l"| "crr" + "n" }
5th  ("e").inject("n") { |"n", "e"| "crrn" + "g" }

     "crrng"

Line 11:

  end

end is connected to our method caesar_cipher and means that we are done defining the method.

Line 13:

  puts caesar_cipher(text, n)

This writes the result of the caesar_cipher method for us.

Project located here: http://www.theodinproject.com/ruby-programming/building-blocks?ref=lnav


Rotate method: http://apidock.com/ruby/Array/rotate
Method arguments: http://www.skorks.com/2009/08/method-arguments-in-ruby/
Zip method: http://dfriedm.github.io/blog/2013/10/12/ruby-zip-method/
Hash: http://ruby-doc.org/core-1.9.3/Hash.html
Each_char: http://ruby-doc.org/core-2.1.0/String.html#method-i-each_char
Inject: http://ruby-doc.org/core-2.2.2/Enumerable.html#method-i-inject
https://blog.udemy.com/ruby-inject/

Advertisements

4 thoughts on “Ruby |Breaking down the Caesar Cipher

  1. As someone putting a lot of effort into learning the standard Ruby library so that I can apply it to solutions like the Caesar Cipher, this is really helpful. The one thing that this solution doesn’t do, however, is address phrases. For a single word it works great. For a phrase with spaces between words it fails. This gives me a good starting point but I’m going to have to figure out logic to handle phrases and even upper vs lower case. Good post, though…again, really helpful!

    Like

    • Thanks for your comment, Martin. I hadn’t thought about also implementing for phrases and upper & lower cases. I’ll also work on it and see if I find a solution. Thanks for visiting!

      Like

  2. Thanks mate. Was scratching my head at getting this to work. You made it relatively straight forward and easy to understand.

    Much obliged.

    Curtis

    Like

Leave a Reply to Martin Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s