Minor documentation updates
[szyfrow.git] / szyfrow / bifid.py
index ed8462febbea43927ca3c775f8b98b372e26536f..f21cce92e125ba6af4cf8c529a6162950bc6ee38 100644 (file)
@@ -1,3 +1,6 @@
+"""Enciphering and deciphering using the [Bifid cipher](https://en.wikipedia.org/wiki/Bifid_cipher). 
+Also attempts to break messages that use a Bifid cipher.
+"""
 import multiprocessing 
 from szyfrow.support.utilities import *
 from szyfrow.support.language_models import *
@@ -28,14 +31,24 @@ def bifid_encipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a,
     >>> bifid_encipher("indiacurry", 'iguana', period=4, fillvalue='x')
     'ibnhgaqltzml'
     """
+
+    if period:
+        if not fillvalue:
+            raise ValueError("fillvalue must be given if period is given")
+        else:
+            p_message = message + pad(len(message), period, fillvalue)
+    else:
+        p_message = message
+
     translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping)
     
-    t_message = message.translate(translation)
+    t_message = p_message.translate(translation)
     pairs0 = [f_grid[l] for l in sanitise(t_message)]
     if period:
-        chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]
-        if len(chunked_pairs[-1]) < period and fillvalue:
-            chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))
+        # chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]
+        # if len(chunked_pairs[-1]) < period and fillvalue:
+        #     chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))
+        chunked_pairs = chunks(pairs0, period, fillvalue=None)
     else:
         chunked_pairs = [pairs0]
     
@@ -59,14 +72,23 @@ def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a,
     >>> bifid_decipher("ibnhgaqltzml", 'iguana', period=4)
     'indiacurryxx'
     """
+    if period:
+        if not fillvalue:
+            raise ValueError("fillvalue must be given if period is given")
+        else:
+            p_message = message + pad(len(message), period, fillvalue)
+    else:
+        p_message = message
+
     translation, f_grid, r_grid = bifid_grid(keyword, wrap_alphabet, letter_mapping)
     
     t_message = message.translate(translation)
     pairs0 = [f_grid[l] for l in sanitise(t_message)]
     if period:
-        chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]
-        if len(chunked_pairs[-1]) < period and fillvalue:
-            chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))
+        # chunked_pairs = [pairs0[i:i+period] for i in range(0, len(pairs0), period)]
+        # if len(chunked_pairs[-1]) < period and fillvalue:
+        #     chunked_pairs[-1] += [f_grid[fillvalue]] * (period - len(chunked_pairs[-1]))
+        chunked_pairs = chunks(pairs0, period, fillvalue=None)
     else:
         chunked_pairs = [pairs0]
         
@@ -80,22 +102,28 @@ def bifid_decipher(message, keyword, wrap_alphabet=KeywordWrapAlphabet.from_a,
     return cat(r_grid[p] for p in pairs1) 
 
 
-def bifid_break_mp(message, wordlist=keywords, fitness=Pletters, max_period=10,
+def bifid_break(message, wordlist=None, fitness=Pletters, max_period=10,
                      number_of_solutions=1, chunksize=500):
     """Breaks a keyword substitution cipher using a dictionary and
     frequency analysis
 
-    >>> bifid_break_mp(bifid_encipher('this is a test message for the ' \
+    If `wordlist` is not specified, use 
+    [`szyfrow.support.langauge_models.keywords`](support/language_models.html#szyfrow.support.language_models.keywords).
+
+    >>> bifid_break(bifid_encipher('this is a test message for the ' \
           'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \
           wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
     (('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...)
-    >>> bifid_break_mp(bifid_encipher('this is a test message for the ' \
+    >>> bifid_break(bifid_encipher('this is a test message for the ' \
           'keyword decipherment', 'elephant', wrap_alphabet=KeywordWrapAlphabet.from_last), \
           wordlist=['cat', 'elephant', 'kangaroo'], \
           number_of_solutions=2) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
     [(('elephant', <KeywordWrapAlphabet.from_last: 2>, 0), -52.834575011...), 
     (('elephant', <KeywordWrapAlphabet.from_largest: 3>, 0), -52.834575011...)]
     """
+    if wordlist is None:
+        wordlist = keywords
+
     with multiprocessing.Pool() as pool:
         helper_args = [(message, word, wrap, period, fitness)
                        for word in wordlist
@@ -110,7 +138,8 @@ def bifid_break_mp(message, wordlist=keywords, fitness=Pletters, max_period=10,
             return sorted(breaks, key=lambda k: k[1], reverse=True)[:number_of_solutions]
 
 def bifid_break_worker(message, keyword, wrap_alphabet, period, fitness):
-    plaintext = bifid_decipher(message, keyword, wrap_alphabet, period=period)
+    plaintext = bifid_decipher(message, keyword, wrap_alphabet, 
+        period=period, fillvalue='e')
     fit = fitness(plaintext)
     return (keyword, wrap_alphabet, period), fit