I've been reading "Think Python" by Allen B. Downey and there's an exercise (8.12) in it where the author asks to create a ROT13 function. I did mine and it partially worked because I was struggling with the uppercases.
Here's a part of the solution provided by the author:
def rotate_letter(letter, n):
"""Rotates a letter by n places. Does not change other chars.
letter: single-letter string
n: int
Returns: single-letter string
"""
if letter.isupper():
start = ord('A')
elif letter.islower():
start = ord('a')
else:
return letter
c = ord(letter) - start
i = (c + n) % 26 + start
return chr(i)
The use of the modulo here makes the function work for the uppercases but I can't figure why ! It's clear that by using it we restart at the beginning of the ASCII values for uppercase but I can't figure the mechanism behind it.
Try breaking it down into steps, and printing out the intermediate numbers. Or, better, running it in an online visualizer.
With, say, the letter 'Q'
and the number 13, you'll end up with:
'Q'.isupper() is true
start = ord('A') = 65
c = ord('Q') - start = 81 - 65 = 16
i = (c + n) % 26 + start = (16 + 13) % 26 + 65 = 29 % 26 + 65 = 3 + 65 = 68
chr(i) is 'D'
As you can see, the magic part is that (16 + 13) % 26
. So let's try running that on every one of the numbers from 0 (for A
) to 25 (for Z
) and see what happens:
>>> for i in range(26):
... print ((i + 13) % 26),
13 14 15 16 17 18 19 20 21 22 23 24 25 0 1 2 3 4 5 6 7 8 9 10 11 12
Adding, then taking the remainder with 26, means that when you get to 26 you go back around to 0. Just like adding 1 hour to 23:00 gets you to 00:00 on a clock (or, if you're an American, adding 1 hour to 12:00 gets you to 1:00).