Let me ruin it for you…
Your basic logic seems both sound and overly complex at the same time.
Here is some input:
- You defined a lot of methods for tasks that already exist. Always assume that Ruby has you covered, for any task you need – even if you’re wrong, searching through the documentation will open your mind to different options at your disposal.
- You ignored the fact that the String is UTF8 encoded. so
c.ord + cypher
might give you an unexpected value that isn’t UTF8 valid. Also, while'ะด'.ord == 1076
, you should be aware that1076.chr
would raise aRangeError
exception. - Assuming you expected ASCII values (using
.ord
and.chr
), we should take into account the fact that bytes are limited to 8 bits (or one octet) – so the highest number for an encrypted byte is 255. - It is better to confine the methods to a namespace using a module – which I will not do here, since I have no idea at what point you are in your studies.
The Caesar algorithm:
-
Turn the string into an array of numbers, each representing a letter… Oh wait, there’s a built in method that does that.
String#bytes
returns an array of integers representing the value of each byte in the binary string. -
Iterate the array of numbers to change the array by adding the cypher number (use
Array#map!
to change the array in-place). Here we just need to remember that bytes have an upper limit value of 255, so we should use the ‘modulu’ methodFixnum#%
. -
Now we just need to convert the array of bytes (with the new value) back to a string… Now we know that the String has a method to turn the string into an array of bytes, so it is only logical that the inverse would also be true – I would go with the
Array#pack
method.
Here’s the gist of it as a sketch:
some_string.bytes.map! {|i| (i +/- cypher) % 255 } .pack 'C*'
# the +/- refers to the question of encoding vs. decoding and won't work.
All you need is one line, really.
I would recommend that you assume that the language you use has almost any method you would ever need. So if you need to perform an action – such as taking a unicode string and converting it into an array of numbers (vs. the limit I introduced, which is a binary octet limit of 255) – Ruby has you covered.
Now, since my answer probably ruined the challenge to some degree – allow me to offer you back the challenge.
Rewrite the solution I offered so that it would work for UTF8 strings (the standard encoding), allowing you a stronger encryption as the cypher’s limit will be the higher UTF8 encoding limit rather than the 255 limit for a byte.
EDIT
Since the “one line” concept made the answer look more advanced than it was, here’s what that code would look like if it was multi-line:
# the encryption method takes a string and a number
def caesar_encrypt string, cypher
# string.bytes will return an array of numbers.
# The numbers are between 0 and 255 (8 bits or 1 byte)
# https://en.wikipedia.org/wiki/Byte
array = string.bytes
# using the Array.map! changes the array in place.
# it's the same as: array = array.map {...}
# the format: { ... } is a one line block, similar to the do ... end.
array.map! do |i|
(i + cypher) % 255
end
# array.pack is used for taking objects in an array and transforming them
# to a string. the 'C*' means we are packing bytes together to a string.
# There are more options such as 'N*' which would pack 32 bit numbers into
# a string. It's a great way to save or manipulate binary data,
# which is at the core of all digital data.
array.pack 'C*'
end
def caesar_decrypt
caesar_encrypt string, (0-cypher)
end
Notice that Array#pack
might be a bit advanced and using your original solution will also work… now that we are using bytes, Fixnum#chr
should work as expected.
def caesar_encrypt string, cypher
array = string.bytes
array.map! do |i|
(i + cypher) % 255
end
out = ""
array.each {|c| out << c.chr}
out
end
3
solved Confused about output in student project