[Solved] Confused about output in student project


Let me ruin it for you…

Your basic logic seems both sound and overly complex at the same time.

Here is some input:

  1. 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.
  2. 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 that 1076.chr would raise a RangeError exception.
  3. 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.
  4. 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:

  1. 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.

  2. 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’ method Fixnum#%.

  3. 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