luke
luke

Reputation: 1578

Keep characters and whitespace in ruby method

Building out a Rot method to solve encryption. I have something that is working but takes out whitespaces and any characters that are included. Was going to use bytes instead of chars then turn it back into a string once I have the byte code but I can't seem to get it working. How would you go about keeping those in place from this code:

code

def rot(x, string, encrypt=true)
  alphabet = Array("A".."Z") + Array("a".."z")
  results = []

  if encrypt == true
    key = Hash[alphabet.zip(alphabet.rotate(x))]
    string.chars.each do |i|
      if ('a'..'z').include? i
        results << key.fetch(i).downcase
      elsif ('A'..'Z').include? i
        results << key.fetch(i).upcase
      end
    end
    return results.join
  else
    key_false = Hash[alphabet.zip(alphabet.rotate(26 - x))]
    string.chars.each do |i|
      if ('a'..'z').include? i
        results << key_false.fetch(i).downcase
      elsif ('A'..'Z').include? i
        results << key_false.fetch(i).upcase
      end
    end
    return results.join
  end
end

puts rot(10, "Hello, World")
=> RovvyGybvn
puts rot(10, "Rovvy, Gybvn", false)
=> HelloWorld

Thanks for your help in advance!

Upvotes: 0

Views: 115

Answers (2)

Stefan
Stefan

Reputation: 114178

I've noticed some issues with your code:

Broken replacement hash

This is the biggest problem - your replacement hash is broken. I'm using a smaller alphabet for demonstration purposes, but this applies to 26 characters as well:

uppercase = Array("A".."C")
lowercase = Array("a".."c")
alphabet = uppercase + lowercase
#=> ["A", "B", "C", "a", "b", "c"]

You build the replacement hash via:

x = 1
key = Hash[alphabet.zip(alphabet.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"a", "a"=>"b", "b"=>"c", "c"=>"A"}

"C"=>"a" and "c"=>"A" are referring to the wrong character case. This happens because you rotate the entire alphabet at once:

alphabet            #=> ["A", "B", "C", "a", "b", "c"]
alphabet.rotate(x)  #=> ["B", "C", "a", "b", "c", "A"]

Instead. you have to rotate the uppercase and lowercase letter separately:

uppercase           #=> ["A", "B", "C"]
uppercase.rotate(x) #=> ["B", "C", "A"]

lowercase           #=> ["a", "b", "c"]
lowercase.rotate(x) #=> ["B", "C", "A"]

and concatenate the rotated parts afterwards. Either:

key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"A", "a"=>"b", "b"=>"c", "c"=>"a"}

or:

key = Hash[(uppercase + lowercase).zip(uppercase.rotate(x) + lowercase.rotate(x))]
#=> {"A"=>"B", "B"=>"C", "C"=>"A", "a"=>"b", "b"=>"c", "c"=>"a"}

Replacing the characters

Back to a full alphabet:

uppercase = Array("A".."Z")
lowercase = Array("a".."z")
x = 10
key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]

Having a working replacement hash makes replacing the characters almost trivial:

string = "Hello, World!"
result = ""
string.each_char { |char| result << key.fetch(char, char) }
result
#=> "Rovvy, Gybvn!"

I've changed result from an array to a string. It also has a << method and you don't have to join it afterwards.

Hash#fetch works almost like Hash#[], but you can pass a default value that is returned if the key is not found in the hash:

 key.fetch("H", "H") #=> "R" (replacement value)
 key.fetch("!", "!") #=> "!" (default value)

Handling encryption / decryption

You're duplicating a lot of code to handle the decryption part. But there's a much easier way - just reverse the direction:

rot(10, "Hello")        #=> "Rovvy"
rot(10, "Rovvy", false) #=> "Hello"
rot(-10, "Rovvy")       #=> "Hello"

So within your code, you can write:

x = -x unless encrypt

Putting it all together

def rot(x, string, encrypt = true)
  uppercase = Array("A".."Z")
  lowercase = Array("a".."z")
  x = -x unless encrypt
  key = Hash[uppercase.zip(uppercase.rotate(x)) + lowercase.zip(lowercase.rotate(x))]
  result = ""
  string.each_char { |char| result << key.fetch(char, char) }
  result
end

rot(10, "Hello, World!")        #=> "Rovvy, Gybvn!"
rot(10, "Rovvy, Gybvn!", false) #=> "Hello, World!"

Upvotes: 0

spickermann
spickermann

Reputation: 106852

Just add to both if blocks an else condition like this:

  if ('a'..'z').include? i
    # ...
  elsif ('A'..'Z').include? i
    # ...
  else
    results << i
  end

Which will add all non A-z characters untouched to the output.

Upvotes: 2

Related Questions