hytriutucx
hytriutucx

Reputation: 1634

Python modulo operator giving unexpected result

I am new to Python and having a problem with the modulo.

Here is the code:

for i in range(ord('a'), ord('z')+1):
    print chr(((i+2) % 97) + 97 )

The exptected result is cdef...a. However I am not getting the desired module behavior of wrapping around once we get to z.

Upvotes: 1

Views: 1553

Answers (5)

Lauritz V. Thaulow
Lauritz V. Thaulow

Reputation: 50995

Others have answered what is wrong with your code. I'd like to present a solution that doesn't have to deal with the modulo at all. I think this is a cleaner way of doing it because you don't have to deal with the nitty-gritty details of converting back and forth using ord and chr.

It also works even if you're dealing with foreign alphabets where the character codes are not all in sequence.

>>> from string import ascii_lowercase
>>> from collections import deque
>>> chrs = deque(ascii_lowercase)
>>> chrs.rotate(-2)
>>> print "".join(chrs)
cdefghijklmnopqrstuvwxyzab

If you're going to use this for some sort of translation or encoding, just build a dict and off you go:

>>> tr = dict(zip(ascii_lowercase, chrs))
>>> "".join(tr.get(x, x) for x in "abcd xyz")
'cdef zab'

Upvotes: 1

Amber
Amber

Reputation: 526743

Because 97 isn't what you want to be wrapping at - you want to be wrapping at ord('z') = 122, and then adding the value of ord('a') (97).

The full math you really need to be doing is to shift into an offset, and then back to the set. For instance...

for i in range(ord('z') - ord('a') + 1): # equivalent to range(26); i.e. 0-25
    print chr(((i+2) % 26) + ord('a')) # results in 2+97 'c', 3+97 'd', etc.

The reason your existing code isn't working is because your i+2 will always be greater than 97 (since your i starts at ord('a') which is 97, and goes up from there), so the % 97 is effectively just - 97, and thus your print line is effectively this:

print chr(((i+2) - 97) + 97 )

which reduces to...

print chr((i+2) - 97 + 97)

which is obviously just print chr(i+2).

Upvotes: 6

Bolo
Bolo

Reputation: 11690

You should work modulo 26 (the number of letters in the English alphabet). This code will work:

for i in range(ord('a'), ord('z') + 1):
   j = i - ord('a')
   print chr(ord('a') + (j + 2) % 26)

Explanation

Consider this indexing template:

A + (j + B) % C

It will map different values of j to the range A … (A + C - 1). Originally, you have chosen A = 97 and C = 97, so you're mapping to the range 97 … 193. I have chosen A = ord('a') = 97 and C = 26, so I'm mapping to the range 97 … 122, i.e., ord('a') … ord('z').

Now about the offset, B. You wanted to rotate two places to the left, and thus you have correctly chosen B = 2. However, in order to this to work, a as the first letter has to be encoded by 0 before the rotation. Hence j = i - ord('a')

Upvotes: 3

jsvk
jsvk

Reputation: 1729

When dealing with mod, looping from a number besides 1 can get confusing. Here's your code refactored to use 1 as a starting point.

for i in range(ord('a'), ord('z')+1):
    print chr( (i-97+2) % 26) + 97 ) #Subtract 97, do our modulo and shift, then add 97

Upvotes: 1

Michael Anderson
Michael Anderson

Reputation: 73490

You need to adjust the value into 0-26 range, modify it, apply the mod, then shift back into the correct character range. Here it is in multiple steps

for i in range(ord('a'), ord('z')+1):
    idx = i - ord('a')
    mod_idx = ( idx + 2 ) % 26
    c = chr( mod_idx + ord('a') )
    print( chr(c) )

And here's the same thing in oneline (too ugly IMO)

for i in range(ord('a'), ord('z')+1):
    print( chr((( i - ord('a')) + 2 ) % 26 + ord('a')) )

Upvotes: 1

Related Questions