4
\$\begingroup\$

I was trying to refactor the following code for Caesar cipher, but it feels overly complicated at the end of the refactoring. Could someone else take a look at it and let me know if I am just overdoing/making it complicated for someone trying to read the code?

My highest priority is to write readable code, so any pointers in that regard would be welcome.

module CASE_WRAP
 def cyclic_next!
 if self == 'z'
 replace 'a'
 return
 end
 if self == 'Z'
 replace 'A'
 return
 end
 self.next!
 end
 def shift(number)
 number.times { self.cyclic_next! }
 self
 end
end
class CaesarCipher
 def self.cipher(message, shift)
 CaesarCipher.new(shift: shift)
 .encrypt message
 end
 def initialize(shift: 0)
 @shift = shift
 end
 def encrypt(message)
 message.gsub(/\w/) do |character|
 character.extend(CASE_WRAP)
 .shift @shift
 end
 end
end

Find tests for this in my repo.

200_success
145k22 gold badges190 silver badges478 bronze badges
asked Feb 10, 2015 at 7:21
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

All the caesar cypher does is rotate an alphabet by n places. If we let the alphabet be an Array, we can use Array#rotate to do the rotating for us. To substitute single characters, there is String#tr. Its name and usage are a little odd, but you'll quickly get used to it.

Basically, it maps characters in a String to characters in another String by their index. So "abc".tr("abc", "123") maps "a" to "1", "b" to "2" and "c" to "3".It expands ranges such as "a-c" first, so you can can write "abc".tr("abc", "123") as "abc".tr("a-c","1-3").

Though the interface is different, here's a simple implementation of the Caesar cypher.

module Caesar
 def self.encrypt(message, shift = 3)
 message.tr("a-zA-Z", rotated_alphabet(shift))
 end
 def self.decrypt(message, shift = 3)
 encrypt(message, 26 - shift)
 end
 def self.rotated_alphabet(shift)
 lower = "a".upto("z").to_a.rotate(shift)
 upper = "A".upto("Z").to_a.rotate(shift)
 lower.concat(upper).join
 end
end
Caesar.decrypt Caesar.encrypt("hi")
# => "hi"

For efficiency, you could change how you calculate the rotated alphabets. Here's an efficient, but not exactly a readable implementation.

 def self.rotated_alphabet(shift)
 lower = (97 + shift).chr # 97 is the ASCII code for "a"
 upper = (65 + shift).chr # 65 is the ASCII code for "A"
 "#{lower}-za-#{lower}#{upper}-ZA-#{upper}"
 end
answered Feb 10, 2015 at 12:24
\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the pointer to tr, I knew the method before; but couldn't see it was ideal for this problem. I have now updated my repo with the refactoring based on your suggestion: github.com/JikkuJose/katas/blob/master/caesar_cipher/01/cc.rb \$\endgroup\$ Commented Feb 11, 2015 at 18:33

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.