''' mod26.py
Modular group, mod 26.
Includes arithmetic operations, including mixed operations with integers.
The complications of mixing types are described below.
The simpler version is mod26A.py.
Python infix operator implementation:
All the infix operators have a method name with double underscores at
either end, like __add__ for +.
The final situation is a bit more complicated if the two operands do not
have the same type and the __add__ method for x does not know what to do
with y, but y may know what to do with x:
Here is more complete pseudocode to return a value for x + y:
if x defines the __add__ method:
calculate ans = x.__add__(y)
if ans is not NotImplemented: # single purpose object in Python
return ans
if y defines the __radd__ method: # add from the Right operand side
calculate ans = y.__radd__(x)
if ans is not NotImplemented:
return ans
throw an Exception: the operation is not defined.
Similarly for __rmul__ and __rsub__.
Like with C++, the operators retain their regular precedence, no matter
what types are being evaluated.
Most built-in functions that may work with arbitrary types
also have a method with double underscores associated.
For instance: __str__ for str __abs__ for abs (absolute value).
'''
class Mod26:
''' Class for mod 26 arithmetic
>>> x = Mod26(4)
>>> y = Mod26(11)
>>> z = Mod26(39)
>>> print x+y
15 mod 26
>>> print z
13 mod 26
>>> print -x
22 mod 26
>>> print z - x
9 mod 26
>>> print x*8
6 mod 26
>>> print x*z
0 mod 26
>>> print x**3
12 mod 26
>>> print x == y
False
>>> print x*y == -8
True
'''
def __init__(self, n=0):
'construct Mod26 object from integer or other Mod26 object'
if isinstance(n, Mod26): # isinstance
n = n.value
assert isinstance(n, (int, long)) # assert syntax: assert booleanExpr
self.value = n % 26 # instance variable not declared: just assigned to
def __str__(self): # used by str built-in function, which is used by print
'Return an informal string representation of object'
return "{0} mod {1}".format(self.value, 26)
def __repr__(self): # used by repr built-in function
'Return a formal string representation, usable in the Shell'
return "Mod{0}({1})".format(26, self.value)
def _toSameType(self, other): # leading underscore: convention for private
'Try to convert to same type, return None otherwise.'
if type(self) == type(other): # avoid conversion overhead if possible
return other
try: # exception syntax
return Mod26(other)
except: # catches all exceptions
return None
def __add__(self, other): # used by + infix operand
'self + other, if defined'
other = self._toSameType(other)
if other is None:
return NotImplemented
return Mod26(self.value + other.value)
def __sub__(self, other): # used by - infix operand
'self - other, if defined'
other = self._toSameType(other)
if other is None:
return NotImplemented
return Mod26(self.value - other.value)
def __neg__(self):# used by - unary operand
'-self'
return Mod26(-self.value)
def __mul__(self, other): # used by * infix operand
'self * other, if defined'
other = self._toSameType(other)
if other is None:
return NotImplemented
return Mod26(self.value * other.value)
def __eq__(self, other): # used by == infix operand
'''self == other, if defined
Allow conversion of other to Mod26 before test. Choice!!!!'''
other = self._toSameType(other)
if other is None:
return False
return self.value == other.value
def __ne__(self, other): # used by != infix operand
'self != other, if defined'
return not self == other
def __pow__(self, p): # used by ** infix operand
'self ** p'
return Mod26(self.value**p)
# operations where only the second operand is a Mod26 (prefix r)
def __radd__(self, other):
'other + self, when other is not a Mod26'
return self + other # commutative, and now Mod26 first
def __rsub__(self, other):
'other - self, when other is not a Mod26'
return -self + other # almost commutative, and now Mod26 first
def __rmul__(self, other):
'other * self, when other is not a Mod26'
return self * other # commutative, and now Mod26 first
def doctests():
'''
More complete tests, including where Exceptions are expected.
Notice that the middle lines of tracebacks are replaced by ...
>>> x = Mod26(4)
>>> y = Mod26(11)
>>> z = Mod26(39)
>>> x != y
True
>>> x != Mod26(4)
False
>>> x != 4
False
>>> 5 - x
Mod26(1)
>>> y - 15
Mod26(22)
>>> 3*x
Mod26(12)
>>> 3+x
Mod26(7)
>>> Mod26(2**100) # tests long operand
Mod26(16)
>>> 3 ** x
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'instance'
>>> Mod26(2.3)
Traceback (most recent call last):
...
AssertionError
'''
if __name__ == '__main__':
import doctest
doctest.testmod() #verbose=True)