Pavol Pľuta Pavol Pľuta - 16 days ago 5
Ruby Question

Easy ROT13 Ruby "programme" mystery

I made a simple ROT13 programme (At school, we started programming in Ruby 1 and half month ago) and I don't understard one thing:

a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
(a.length+1).times do |i|
print a[i + 13]
if i>13
print a[i %14]
end

end


Output:
NOPQRSTUVWXYZABCDEFGHIJKLM


If I won't add +1 after the a.length, the iteration ends with the letter L. However, If I just use
print a[i]
inside the iteration, it normally starts with A and ends with Z with no +1 addition needed.

Can someone explain this mystery for me? Sorry for bothering with such a irrelevant question, but I'm really curious and want to get better and better.

Thank you very much!

Answer

As you may know, the .times loop invokes the block specified number of times, passing into each iteration an incremented value.

If we say 26.times {|i| puts i} it will print values from 0 to 25. Up to, but not including the last value.

Now let's walk through the loop. At first iteration, i is 0. So we print 14th character of the string, "N" (at index 13, zero-based). We don't go into the condition because 0 is not greater than 13. On second iteration we print 15th character, "O". And keep doing this, until we reach i=14.

At this point, the "magic" begins. First, we attempt to print 27th character of the string. There's no such character so we print literally nothing. Then the condition is triggered and we go in.

i % 14 equals 0, so we print zeroth character, "A". Next iteration we print character at index 1 (15 % 14) and so on, until .times finishes its iteration and stops calling the block. Now, for this logic to work, the last value for i must be 26, so that we get 12 in i % 14 and print "M".

Length of the entire string is 26. Remember, .times counts up to but not including the number? That's why we add one to the length, so that it counts from 0 to 26. That's the mystery.

There are many-many ways of improving this code, and you'll learn about them in time. :)

Update

I knew something looked odd about the code. And, of course, there's a bug. When i is 13 we don't print the first time and we don't go into the condition. We waste one iteration. This is a classic example of "off by 1" class of errors. Here's fixed version of code that doesn't waste iterations and contains no mysteries:

a.length.times do |i|
  print  a[i + 13]
  if i > 12
    print a[i % 13]
  end
end
Comments