Minor documentation updates
[szyfrow.git] / szyfrow / affine.py
index a5647be20e3b30abd5573ac087eac7da1f1f5d2f..955363782ce90bfbc2e6ca31b335d780c1013aea 100644 (file)
@@ -1,7 +1,23 @@
-from support.utilities import *
-from support.language_models import *
-from logger import logger
+"""Enciphering and deciphering using the [affine cipher](https://en.wikipedia.org/wiki/Affine_cipher). 
+Also attempts to break messages that use an affine cipher.
 
+The affine cipher operates one letter at a time. It converts each letter to a 
+number, then enciphers that number using a multiplier and a number. The result 
+is taken mod 26 and converted back into a letter.
+
+For a multiplier _m_ and adder _a_, a letter converted to number _x_ is 
+enciphered as _E(x)_ = (_m_ _x_ + _a_) mod 26. Deciphering uses the modular 
+inverse of _m_, _m_⁻¹, so that _D(x)_ = _m_⁻¹ (_x_ - _a_) mod 26.
+
+If `one_based` is `True`, the conversion between letters and numbers maps 
+'a' → 1, 'b' → 2, … 'z'→ 26 and the `mod` function is adjusted to keep 
+numbers in this range during enciphering and deciphering. If `one_based` is 
+`False`, the conversion maps 'a' → 0, 'b' → 1, … 'z'→ 25 and `mod` behaves
+normally.
+"""
+
+from szyfrow.support.utilities import *
+from szyfrow.support.language_models import *
 
 modular_division_table = {
     (multiplier, (multiplier * plaintext) % 26): plaintext
@@ -11,7 +27,10 @@ modular_division_table = {
 
 
 def affine_encipher_letter(accented_letter, multiplier=1, adder=0, one_based=True):
-    """Encipher a letter, given a multiplier and adder
+    """Encipher a letter, given a multiplier and adder.
+
+    Accented version of latin letters (such as é and ö) are converted to their
+    non-accented versions before encryption.
     
     >>> cat(affine_encipher_letter(l, 3, 5, True) \
             for l in string.ascii_letters)
@@ -46,9 +65,6 @@ def affine_decipher_letter(letter, multiplier=1, adder=0, one_based=True):
     if letter in string.ascii_letters:
         cipher_number = pos(letter)
         if one_based: cipher_number += 1
-        # plaintext_number = ( 
-        #     modular_division_table[multiplier]
-        #                           [(cipher_number - adder) % 26])
         plaintext_number = ( 
             modular_division_table[multiplier, (cipher_number - adder) % 26]
             )
@@ -85,7 +101,11 @@ def affine_decipher(message, multiplier=1, adder=0, one_based=True):
 
 
 def affine_break(message, fitness=Pletters):
-    """Breaks an affine cipher using frequency analysis
+    """Breaks an affine cipher using frequency analysis.
+
+    It tries all possible combinations of multiplier, adder, and one_based,
+    scores the fitness of the text decipherd with each combination, and returns
+    the key that produces the most fit deciphered text.
 
     >>> affine_break('lmyfu bkuusd dyfaxw claol psfaom jfasd snsfg jfaoe ls ' \
           'omytd jlaxe mh jm bfmibj umis hfsul axubafkjamx. ls kffkxwsd jls ' \
@@ -106,18 +126,10 @@ def affine_break(message, fitness=Pletters):
                 plaintext = affine_decipher(sanitised_message,
                                             multiplier, adder, one_based)
                 fit = fitness(plaintext)
-                logger.debug('Affine break attempt using key {0}x+{1} ({2}) '
-                             'gives fit of {3} and decrypt starting: {4}'.
-                             format(multiplier, adder, one_based, fit,
-                                    plaintext[:50]))
                 if fit > best_fit:
                     best_fit = fit
                     best_multiplier = multiplier
                     best_adder = adder
                     best_one_based = one_based
-    logger.info('Affine break best fit with key {0}x+{1} ({2}) gives fit of '
-                '{3} and decrypt starting: {4}'.format(
-                    best_multiplier, best_adder, best_one_based, best_fit,
-                    affine_decipher(sanitised_message, best_multiplier,
-                                    best_adder, best_one_based)[:50]))
+
     return (best_multiplier, best_adder, best_one_based), best_fit