More tweaking to conform with linting
authorNeil Smith <neil.git@njae.me.uk>
Sun, 13 Jul 2014 22:13:09 +0000 (23:13 +0100)
committerNeil Smith <neil.git@njae.me.uk>
Sun, 13 Jul 2014 22:13:09 +0000 (23:13 +0100)
cipher.py
cipherbreak.py
language_models.py
norms.py
segment.py

index 1656720d64867dc71f547f746f202776792bf633..3783d577d6f1bf4f105bfc83ca5069718650c4ff 100644 (file)
--- a/cipher.py
+++ b/cipher.py
@@ -44,7 +44,7 @@ def combine_every_nth(split_text):
     'abcdefghijklmnopqrstuvwxyz'
     """
     return ''.join([''.join(l)
     'abcdefghijklmnopqrstuvwxyz'
     """
     return ''.join([''.join(l)
-        for l in zip_longest(*split_text, fillvalue='')])
+                    for l in zip_longest(*split_text, fillvalue='')])
 
 def chunks(text, n, fillvalue=None):
     """Split a text into chunks of n characters
 
 def chunks(text, n, fillvalue=None):
     """Split a text into chunks of n characters
@@ -79,7 +79,7 @@ def transpose(items, transposition):
 
 def untranspose(items, transposition):
     """Undoes a transpose
 
 def untranspose(items, transposition):
     """Undoes a transpose
-    
+
     >>> untranspose(['a', 'b', 'c', 'd'], [0,1,2,3])
     ['a', 'b', 'c', 'd']
     >>> untranspose(['d', 'b', 'c', 'a'], [3,1,2,0])
     >>> untranspose(['a', 'b', 'c', 'd'], [0,1,2,3])
     ['a', 'b', 'c', 'd']
     >>> untranspose(['d', 'b', 'c', 'a'], [3,1,2,0])
@@ -183,7 +183,7 @@ def caesar_decipher(message, shift):
     return caesar_encipher(message, -shift)
 
 def affine_encipher_letter(accented_letter, multiplier=1, adder=0,
     return caesar_encipher(message, -shift)
 
 def affine_encipher_letter(accented_letter, multiplier=1, adder=0,
-        one_based=True):
+                           one_based=True):
     """Encipher a letter, given a multiplier and adder
     >>> ''.join([affine_encipher_letter(l, 3, 5, True) \
             for l in string.ascii_uppercase])
     """Encipher a letter, given a multiplier and adder
     >>> ''.join([affine_encipher_letter(l, 3, 5, True) \
             for l in string.ascii_uppercase])
@@ -225,7 +225,8 @@ def affine_decipher_letter(letter, multiplier=1, adder=0, one_based=True):
         if one_based: cipher_number += 1
         plaintext_number = (
             modular_division_table[multiplier]
         if one_based: cipher_number += 1
         plaintext_number = (
             modular_division_table[multiplier]
-                                  [(cipher_number - adder) % 26] )
+                                  [(cipher_number - adder) % 26]
+                            )
         if one_based: plaintext_number -= 1
         return chr(plaintext_number % 26 + alphabet_start)
     else:
         if one_based: plaintext_number -= 1
         return chr(plaintext_number % 26 + alphabet_start)
     else:
@@ -254,7 +255,7 @@ def affine_decipher(message, multiplier=1, adder=0, one_based=True):
     return ''.join(enciphered)
 
 
     return ''.join(enciphered)
 
 
-class Keyword_wrap_alphabet(Enum):
+class KeywordWrapAlphabet(Enum):
     """Ways of wrapping the alphabet for keyword-based substitution ciphers."""
     from_a = 1
     from_last = 2
     """Ways of wrapping the alphabet for keyword-based substitution ciphers."""
     from_a = 1
     from_last = 2
@@ -262,25 +263,25 @@ class Keyword_wrap_alphabet(Enum):
 
 
 def keyword_cipher_alphabet_of(keyword,
 
 
 def keyword_cipher_alphabet_of(keyword,
-        wrap_alphabet=Keyword_wrap_alphabet.from_a):
+        wrap_alphabet=KeywordWrapAlphabet.from_a):
     """Find the cipher alphabet given a keyword.
     wrap_alphabet controls how the rest of the alphabet is added
     after the keyword.
 
     >>> keyword_cipher_alphabet_of('bayes')
     'bayescdfghijklmnopqrtuvwxz'
     """Find the cipher alphabet given a keyword.
     wrap_alphabet controls how the rest of the alphabet is added
     after the keyword.
 
     >>> keyword_cipher_alphabet_of('bayes')
     'bayescdfghijklmnopqrtuvwxz'
-    >>> keyword_cipher_alphabet_of('bayes', Keyword_wrap_alphabet.from_a)
+    >>> keyword_cipher_alphabet_of('bayes', KeywordWrapAlphabet.from_a)
     'bayescdfghijklmnopqrtuvwxz'
     'bayescdfghijklmnopqrtuvwxz'
-    >>> keyword_cipher_alphabet_of('bayes', Keyword_wrap_alphabet.from_last)
+    >>> keyword_cipher_alphabet_of('bayes', KeywordWrapAlphabet.from_last)
     'bayestuvwxzcdfghijklmnopqr'
     'bayestuvwxzcdfghijklmnopqr'
-    >>> keyword_cipher_alphabet_of('bayes', Keyword_wrap_alphabet.from_largest)
+    >>> keyword_cipher_alphabet_of('bayes', KeywordWrapAlphabet.from_largest)
     'bayeszcdfghijklmnopqrtuvwx'
     """
     'bayeszcdfghijklmnopqrtuvwx'
     """
-    if wrap_alphabet == Keyword_wrap_alphabet.from_a:
+    if wrap_alphabet == KeywordWrapAlphabet.from_a:
         cipher_alphabet = ''.join(deduplicate(sanitise(keyword) +
                                               string.ascii_lowercase))
     else:
         cipher_alphabet = ''.join(deduplicate(sanitise(keyword) +
                                               string.ascii_lowercase))
     else:
-        if wrap_alphabet == Keyword_wrap_alphabet.from_last:
+        if wrap_alphabet == KeywordWrapAlphabet.from_last:
             last_keyword_letter = deduplicate(sanitise(keyword))[-1]
         else:
             last_keyword_letter = sorted(sanitise(keyword))[-1]
             last_keyword_letter = deduplicate(sanitise(keyword))[-1]
         else:
             last_keyword_letter = sorted(sanitise(keyword))[-1]
@@ -294,7 +295,7 @@ def keyword_cipher_alphabet_of(keyword,
 
 
 def keyword_encipher(message, keyword,
 
 
 def keyword_encipher(message, keyword,
-        wrap_alphabet=Keyword_wrap_alphabet.from_a):
+                     wrap_alphabet=KeywordWrapAlphabet.from_a):
     """Enciphers a message with a keyword substitution cipher.
     wrap_alphabet controls how the rest of the alphabet is added
     after the keyword.
     """Enciphers a message with a keyword substitution cipher.
     wrap_alphabet controls how the rest of the alphabet is added
     after the keyword.
@@ -304,18 +305,19 @@ def keyword_encipher(message, keyword,
 
     >>> keyword_encipher('test message', 'bayes')
     'rsqr ksqqbds'
 
     >>> keyword_encipher('test message', 'bayes')
     'rsqr ksqqbds'
-    >>> keyword_encipher('test message', 'bayes', Keyword_wrap_alphabet.from_a)
+    >>> keyword_encipher('test message', 'bayes', KeywordWrapAlphabet.from_a)
     'rsqr ksqqbds'
     'rsqr ksqqbds'
-    >>> keyword_encipher('test message', 'bayes', Keyword_wrap_alphabet.from_last)
+    >>> keyword_encipher('test message', 'bayes', KeywordWrapAlphabet.from_last)
     'lskl dskkbus'
     'lskl dskkbus'
-    >>> keyword_encipher('test message', 'bayes', Keyword_wrap_alphabet.from_largest)
+    >>> keyword_encipher('test message', 'bayes', KeywordWrapAlphabet.from_largest)
     'qspq jsppbcs'
     """
     cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
     cipher_translation = ''.maketrans(string.ascii_lowercase, cipher_alphabet)
     return unaccent(message).lower().translate(cipher_translation)
 
     'qspq jsppbcs'
     """
     cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
     cipher_translation = ''.maketrans(string.ascii_lowercase, cipher_alphabet)
     return unaccent(message).lower().translate(cipher_translation)
 
-def keyword_decipher(message, keyword, wrap_alphabet=Keyword_wrap_alphabet.from_a):
+def keyword_decipher(message, keyword, 
+                     wrap_alphabet=KeywordWrapAlphabet.from_a):
     """Deciphers a message with a keyword substitution cipher.
     wrap_alphabet controls how the rest of the alphabet is added
     after the keyword.
     """Deciphers a message with a keyword substitution cipher.
     wrap_alphabet controls how the rest of the alphabet is added
     after the keyword.
@@ -325,11 +327,11 @@ def keyword_decipher(message, keyword, wrap_alphabet=Keyword_wrap_alphabet.from_
 
     >>> keyword_decipher('rsqr ksqqbds', 'bayes')
     'test message'
 
     >>> keyword_decipher('rsqr ksqqbds', 'bayes')
     'test message'
-    >>> keyword_decipher('rsqr ksqqbds', 'bayes', Keyword_wrap_alphabet.from_a)
+    >>> keyword_decipher('rsqr ksqqbds', 'bayes', KeywordWrapAlphabet.from_a)
     'test message'
     'test message'
-    >>> keyword_decipher('lskl dskkbus', 'bayes', Keyword_wrap_alphabet.from_last)
+    >>> keyword_decipher('lskl dskkbus', 'bayes', KeywordWrapAlphabet.from_last)
     'test message'
     'test message'
-    >>> keyword_decipher('qspq jsppbcs', 'bayes', Keyword_wrap_alphabet.from_largest)
+    >>> keyword_decipher('qspq jsppbcs', 'bayes', KeywordWrapAlphabet.from_largest)
     'test message'
     """
     cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
     'test message'
     """
     cipher_alphabet = keyword_cipher_alphabet_of(keyword, wrap_alphabet)
@@ -357,14 +359,14 @@ def vigenere_decipher(message, keyword):
     pairs = zip(message, cycle(shifts))
     return ''.join([caesar_decipher_letter(l, k) for l, k in pairs])
 
     pairs = zip(message, cycle(shifts))
     return ''.join([caesar_decipher_letter(l, k) for l, k in pairs])
 
-beaufort_encipher=vigenere_decipher
-beaufort_decipher=vigenere_encipher
+beaufort_encipher = vigenere_decipher
+beaufort_decipher = vigenere_encipher
 
 
 def transpositions_of(keyword):
     """Finds the transpostions given by a keyword. For instance, the keyword
     'clever' rearranges to 'celrv', so the first column (0) stays first, the
 
 
 def transpositions_of(keyword):
     """Finds the transpostions given by a keyword. For instance, the keyword
     'clever' rearranges to 'celrv', so the first column (0) stays first, the
-    second column (1) moves to third, the third column (2) moves to second, 
+    second column (1) moves to third, the third column (2) moves to second,
     and so on.
 
     If passed a tuple, assume it's already a transposition and just return it.
     and so on.
 
     If passed a tuple, assume it's already a transposition and just return it.
@@ -408,8 +410,8 @@ def pad(message_len, group_len, fillvalue):
     return padding
 
 def column_transposition_encipher(message, keyword, fillvalue=' ',
     return padding
 
 def column_transposition_encipher(message, keyword, fillvalue=' ',
-      fillcolumnwise=False,
-      emptycolumnwise=False):
+                                  fillcolumnwise=False,
+                                  emptycolumnwise=False):
     """Enciphers using the column transposition cipher.
     Message is padded to allow all rows to be the same length.
 
     """Enciphers using the column transposition cipher.
     Message is padded to allow all rows to be the same length.
 
@@ -457,8 +459,8 @@ def column_transposition_encipher(message, keyword, fillvalue=' ',
         return ''.join(chain(*transposed))
 
 def column_transposition_decipher(message, keyword, fillvalue=' ',
         return ''.join(chain(*transposed))
 
 def column_transposition_decipher(message, keyword, fillvalue=' ',
-      fillcolumnwise=False,
-      emptycolumnwise=False):
+                                  fillcolumnwise=False,
+                                  emptycolumnwise=False):
     """Deciphers using the column transposition cipher.
     Message is padded to allow all rows to be the same length.
 
     """Deciphers using the column transposition cipher.
     Message is padded to allow all rows to be the same length.
 
@@ -508,12 +510,12 @@ def scytale_encipher(message, rows, fillvalue=' '):
     """
     transpositions = [i for i in range(rows)]
     return column_transposition_encipher(message, transpositions,
     """
     transpositions = [i for i in range(rows)]
     return column_transposition_encipher(message, transpositions,
-        fillvalue=fillvalue, fillcolumnwise=True, emptycolumnwise=False)
+            fillvalue=fillvalue, fillcolumnwise=True, emptycolumnwise=False)
 
 def scytale_decipher(message, rows):
     """Deciphers using the scytale transposition cipher.
     Assumes the message is padded so that all rows are the same length.
 
 def scytale_decipher(message, rows):
     """Deciphers using the scytale transposition cipher.
     Assumes the message is padded so that all rows are the same length.
-    
+
     >>> scytale_decipher('tcnhkfeboqrxuo iw ', 3)
     'thequickbrownfox  '
     >>> scytale_decipher('tubnhirfecooqkwx', 4)
     >>> scytale_decipher('tcnhkfeboqrxuo iw ', 3)
     'thequickbrownfox  '
     >>> scytale_decipher('tubnhirfecooqkwx', 4)
@@ -600,14 +602,14 @@ class PocketEnigma(object):
         ValueError: Wheel specification does not contain 26 letters
         """
         if len(wheel_spec) != 13:
         ValueError: Wheel specification does not contain 26 letters
         """
         if len(wheel_spec) != 13:
-            raise ValueError("Wheel specification has {} pairs, requires 13".
-                format(len(wheel_spec)))
+            raise ValueError("Wheel specification has {} pairs, requires"
+                             " 13".format(len(wheel_spec)))
         for p in wheel_spec:
             if len(p) != 2:
                 raise ValueError("Not all mappings in wheel specification"
         for p in wheel_spec:
             if len(p) != 2:
                 raise ValueError("Not all mappings in wheel specification"
-                    "have two elements")
-        if len(set([p[0] for p in wheel_spec] + 
-                    [p[1] for p in wheel_spec])) != 26:
+                                 "have two elements")
+        if len(set([p[0] for p in wheel_spec] +
+                   [p[1] for p in wheel_spec])) != 26:
             raise ValueError("Wheel specification does not contain 26 letters")
 
     def encipher_letter(self, letter):
             raise ValueError("Wheel specification does not contain 26 letters")
 
     def encipher_letter(self, letter):
index 3639da5c68c1d32621b73129c9495a91ae73539c..b28b763250bd10115545a34a25a86b08c3a634e9 100644 (file)
@@ -1,12 +1,15 @@
+"""A set of functions to break the ciphers give in ciphers.py.
+"""
+
 import string
 import collections
 import norms
 import logging
 import random
 import string
 import collections
 import norms
 import logging
 import random
-from itertools import zip_longest, cycle, permutations, starmap
+import math
+from itertools import starmap
 from segment import segment
 from multiprocessing import Pool
 from segment import segment
 from multiprocessing import Pool
-from math import log10
 
 import matplotlib.pyplot as plt
 
 
 import matplotlib.pyplot as plt
 
@@ -32,7 +35,7 @@ for word in keywords:
 
 def frequencies(text):
     """Count the number of occurrences of each character in text
 
 def frequencies(text):
     """Count the number of occurrences of each character in text
-    
+
     >>> sorted(frequencies('abcdefabc').items())
     [('a', 2), ('b', 2), ('c', 2), ('d', 1), ('e', 1), ('f', 1)]
     >>> sorted(frequencies('the quick brown fox jumped over the lazy ' \
     >>> sorted(frequencies('abcdefabc').items())
     [('a', 2), ('b', 2), ('c', 2), ('d', 1), ('e', 1), ('f', 1)]
     >>> sorted(frequencies('the quick brown fox jumped over the lazy ' \
@@ -48,7 +51,7 @@ def frequencies(text):
      ('W', 1), ('a', 1), ('c', 1), ('d', 1), ('e', 4), ('f', 1), ('h', 2),
      ('i', 1), ('j', 1), ('k', 1), ('l', 1), ('m', 1), ('o', 2), ('p', 1),
      ('r', 1), ('t', 1), ('u', 2), ('v', 1), ('x', 1), ('y', 1), ('z', 1)]
      ('W', 1), ('a', 1), ('c', 1), ('d', 1), ('e', 4), ('f', 1), ('h', 2),
      ('i', 1), ('j', 1), ('k', 1), ('l', 1), ('m', 1), ('o', 2), ('p', 1),
      ('r', 1), ('t', 1), ('u', 2), ('v', 1), ('x', 1), ('y', 1), ('z', 1)]
-         >>> sorted(frequencies(sanitise('The Quick BROWN fox jumped! over... '\
+    >>> sorted(frequencies(sanitise('The Quick BROWN fox jumped! over... '\
          'the (9lazy) DOG')).items()) # doctest: +NORMALIZE_WHITESPACE
     [('a', 1), ('b', 1), ('c', 1), ('d', 2), ('e', 4), ('f', 1), ('g', 1),
      ('h', 2), ('i', 1), ('j', 1), ('k', 1), ('l', 1), ('m', 1), ('n', 1),
          'the (9lazy) DOG')).items()) # doctest: +NORMALIZE_WHITESPACE
     [('a', 1), ('b', 1), ('c', 1), ('d', 2), ('e', 4), ('f', 1), ('g', 1),
      ('h', 2), ('i', 1), ('j', 1), ('k', 1), ('l', 1), ('m', 1), ('n', 1),
@@ -80,8 +83,8 @@ def caesar_break(message, fitness=Pletters):
         plaintext = caesar_decipher(sanitised_message, shift)
         fit = fitness(plaintext)
         logger.debug('Caesar break attempt using key {0} gives fit of {1} '
         plaintext = caesar_decipher(sanitised_message, shift)
         fit = fitness(plaintext)
         logger.debug('Caesar break attempt using key {0} gives fit of {1} '
-            'and decrypt starting: {2}'.format(shift, fit,
-                plaintext[:50]))
+                     'and decrypt starting: {2}'.format(shift, fit,
+                                                        plaintext[:50]))
         if fit > best_fit:
             best_fit = fit
             best_shift = shift
         if fit > best_fit:
             best_fit = fit
             best_shift = shift
@@ -109,12 +112,12 @@ def affine_break(message, fitness=Pletters):
     for one_based in [True, False]:
         for multiplier in [x for x in range(1, 26, 2) if x != 13]:
             for adder in range(26):
     for one_based in [True, False]:
         for multiplier in [x for x in range(1, 26, 2) if x != 13]:
             for adder in range(26):
-                plaintext = affine_decipher(sanitised_message, 
+                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}'.
                                             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, 
+                             format(multiplier, adder, one_based, fit,
                                     plaintext[:50]))
                 if fit > best_fit:
                     best_fit = fit
                                     plaintext[:50]))
                 if fit > best_fit:
                     best_fit = fit
@@ -125,22 +128,22 @@ def affine_break(message, fitness=Pletters):
                 '{3} and decrypt starting: {4}'.format(
                     best_multiplier, best_adder, best_one_based, best_fit,
                     affine_decipher(sanitised_message, best_multiplier,
                 '{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]))
+                                    best_adder, best_one_based)[:50]))
     return (best_multiplier, best_adder, best_one_based), best_fit
 
 def keyword_break(message, wordlist=keywords, fitness=Pletters):
     return (best_multiplier, best_adder, best_one_based), best_fit
 
 def keyword_break(message, wordlist=keywords, fitness=Pletters):
-    """Breaks a keyword substitution cipher using a dictionary and 
-    frequency analysis
+    """Breaks a keyword substitution cipher using a dictionary and
+    frequency analysis.
 
     >>> keyword_break(keyword_encipher('this is a test message for the ' \
 
     >>> keyword_break(keyword_encipher('this is a test message for the ' \
-          'keyword decipherment', 'elephant', Keyword_wrap_alphabet.from_last), \
+          'keyword decipherment', 'elephant', KeywordWrapAlphabet.from_last), \
           wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
           wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
-    (('elephant', <Keyword_wrap_alphabet.from_last: 2>), -52.834575011...)
+    (('elephant', <KeywordWrapAlphabet.from_last: 2>), -52.834575011...)
     """
     best_keyword = ''
     best_wrap_alphabet = True
     best_fit = float("-inf")
     """
     best_keyword = ''
     best_wrap_alphabet = True
     best_fit = float("-inf")
-    for wrap_alphabet in Keyword_wrap_alphabet:
+    for wrap_alphabet in KeywordWrapAlphabet:
         for keyword in wordlist:
             plaintext = keyword_decipher(message, keyword, wrap_alphabet)
             fit = fitness(plaintext)
         for keyword in wordlist:
             plaintext = keyword_decipher(message, keyword, wrap_alphabet)
             fit = fitness(plaintext)
@@ -159,19 +162,20 @@ def keyword_break(message, wordlist=keywords, fitness=Pletters):
                                          best_wrap_alphabet))[:50]))
     return (best_keyword, best_wrap_alphabet), best_fit
 
                                          best_wrap_alphabet))[:50]))
     return (best_keyword, best_wrap_alphabet), best_fit
 
-def keyword_break_mp(message, wordlist=keywords, fitness=Pletters, chunksize=500):
-    """Breaks a keyword substitution cipher using a dictionary and 
+def keyword_break_mp(message, wordlist=keywords, fitness=Pletters,
+                     chunksize=500):
+    """Breaks a keyword substitution cipher using a dictionary and
     frequency analysis
 
     >>> keyword_break_mp(keyword_encipher('this is a test message for the ' \
     frequency analysis
 
     >>> keyword_break_mp(keyword_encipher('this is a test message for the ' \
-          'keyword decipherment', 'elephant', Keyword_wrap_alphabet.from_last), \
+          'keyword decipherment', 'elephant', KeywordWrapAlphabet.from_last), \
           wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
           wordlist=['cat', 'elephant', 'kangaroo']) # doctest: +ELLIPSIS
-    (('elephant', <Keyword_wrap_alphabet.from_last: 2>), -52.834575011...)
+    (('elephant', <KeywordWrapAlphabet.from_last: 2>), -52.834575011...)
     """
     with Pool() as pool:
         helper_args = [(message, word, wrap, fitness)
                        for word in wordlist
     """
     with Pool() as pool:
         helper_args = [(message, word, wrap, fitness)
                        for word in wordlist
-                       for wrap in Keyword_wrap_alphabet]
+                       for wrap in KeywordWrapAlphabet]
         # Gotcha: the helper function here needs to be defined at the top level
         #   (limitation of Pool.starmap)
         breaks = pool.starmap(keyword_break_worker, helper_args, chunksize)
         # Gotcha: the helper function here needs to be defined at the top level
         #   (limitation of Pool.starmap)
         breaks = pool.starmap(keyword_break_worker, helper_args, chunksize)
@@ -185,14 +189,14 @@ def keyword_break_worker(message, keyword, wrap_alphabet, fitness):
                      wrap_alphabet, fit, sanitise(plaintext)[:50]))
     return (keyword, wrap_alphabet), fit
 
                      wrap_alphabet, fit, sanitise(plaintext)[:50]))
     return (keyword, wrap_alphabet), fit
 
-def monoalphabetic_break_hillclimbing(message, max_iterations = 10000000, 
+def monoalphabetic_break_hillclimbing(message, max_iterations=10000000, 
         fitness=Pletters):
     ciphertext = unaccent(message).lower()
     alphabet = list(string.ascii_lowercase)
     random.shuffle(alphabet)
     alphabet = ''.join(alphabet)
     return monoalphabetic_break_hillclimbing_worker(ciphertext, alphabet,
         fitness=Pletters):
     ciphertext = unaccent(message).lower()
     alphabet = list(string.ascii_lowercase)
     random.shuffle(alphabet)
     alphabet = ''.join(alphabet)
     return monoalphabetic_break_hillclimbing_worker(ciphertext, alphabet,
-        max_iterations, fitness)
+                                                    max_iterations, fitness)
 
 def monoalphabetic_break_hillclimbing_mp(message, workers=10, 
         max_iterations = 10000000, fitness=Pletters, chunksize=1):
 
 def monoalphabetic_break_hillclimbing_mp(message, workers=10, 
         max_iterations = 10000000, fitness=Pletters, chunksize=1):
@@ -205,10 +209,10 @@ def monoalphabetic_break_hillclimbing_mp(message, workers=10,
         worker_args.append((ciphertext, alphabet, max_iterations, fitness))
     with Pool() as pool:
         breaks = pool.starmap(monoalphabetic_break_hillclimbing_worker,
         worker_args.append((ciphertext, alphabet, max_iterations, fitness))
     with Pool() as pool:
         breaks = pool.starmap(monoalphabetic_break_hillclimbing_worker,
-            worker_args, chunksize)
+                              worker_args, chunksize)
     return max(breaks, key=lambda k: k[1])
 
     return max(breaks, key=lambda k: k[1])
 
-def monoalphabetic_break_hillclimbing_worker(message, alphabet, 
+def monoalphabetic_break_hillclimbing_worker(message, alphabet,
         max_iterations, fitness):
     def swap(letters, i, j):
         if i > j:
         max_iterations, fitness):
     def swap(letters, i, j):
         if i > j:
@@ -216,8 +220,8 @@ def monoalphabetic_break_hillclimbing_worker(message, alphabet,
         if i == j:
             return letters
         else:
         if i == j:
             return letters
         else:
-            return letters[:i] + letters[j] + letters[i+1:j] + 
-                    letters[i] + letters[j+1:]
+            return (letters[:i] + letters[j] + letters[i+1:j] + letters[i] +
+                    letters[j+1:])
     best_alphabet = alphabet
     best_fitness = float('-inf')
     for i in range(max_iterations):
     best_alphabet = alphabet
     best_fitness = float('-inf')
     for i in range(max_iterations):
@@ -231,17 +235,17 @@ def monoalphabetic_break_hillclimbing_worker(message, alphabet,
     return best_alphabet, best_fitness
 
 
     return best_alphabet, best_fitness
 
 
-def column_transposition_break_mp(message, translist=transpositions, 
-                     fitness=Pbigrams, chunksize=500):
-    """Breaks a column transposition cipher using a dictionary and 
+def column_transposition_break_mp(message, translist=transpositions,
+                                  fitness=Pbigrams, chunksize=500):
+    """Breaks a column transposition cipher using a dictionary and
     n-gram frequency analysis
 
     >>> column_transposition_break_mp(column_transposition_encipher(sanitise( \
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
     n-gram frequency analysis
 
     >>> column_transposition_break_mp(column_transposition_encipher(sanitise( \
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
-             first entering a neighbourhood, this truth is so well fixed in the \
-             minds of the surrounding families, that he is considered the \
+             first entering a neighbourhood, this truth is so well fixed in \
+             the minds of the surrounding families, that he is considered the \
              rightful property of some one or other of their daughters."), \
         'encipher'), \
         translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
              rightful property of some one or other of their daughters."), \
         'encipher'), \
         translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
@@ -252,8 +256,8 @@ def column_transposition_break_mp(message, translist=transpositions,
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
-             first entering a neighbourhood, this truth is so well fixed in the \
-             minds of the surrounding families, that he is considered the \
+             first entering a neighbourhood, this truth is so well fixed in \
+             the minds of the surrounding families, that he is considered the \
              rightful property of some one or other of their daughters."), \
         'encipher'), \
         translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
              rightful property of some one or other of their daughters."), \
         'encipher'), \
         translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \
@@ -263,21 +267,21 @@ def column_transposition_break_mp(message, translist=transpositions,
     (((2, 0, 5, 3, 1, 4, 6), False, False), -997.0129085...)
     """
     with Pool() as pool:
     (((2, 0, 5, 3, 1, 4, 6), False, False), -997.0129085...)
     """
     with Pool() as pool:
-        helper_args = [(message, trans, fillcolumnwise, emptycolumnwise, 
-                          fitness) 
-                       for trans in translist.keys() 
+        helper_args = [(message, trans, fillcolumnwise, emptycolumnwise,
+                        fitness)
+                       for trans in translist.keys()
                        for fillcolumnwise in [True, False]
                        for emptycolumnwise in [True, False]]
                        for fillcolumnwise in [True, False]
                        for emptycolumnwise in [True, False]]
-        # Gotcha: the helper function here needs to be defined at the top level 
+        # Gotcha: the helper function here needs to be defined at the top level
         #   (limitation of Pool.starmap)
         #   (limitation of Pool.starmap)
-        breaks = pool.starmap(column_transposition_break_worker, 
-          helper_args, chunksize) 
+        breaks = pool.starmap(column_transposition_break_worker,
+                              helper_args, chunksize) 
         return max(breaks, key=lambda k: k[1])
 column_transposition_break = column_transposition_break_mp
 
         return max(breaks, key=lambda k: k[1])
 column_transposition_break = column_transposition_break_mp
 
-def column_transposition_break_worker(message, transposition, 
+def column_transposition_break_worker(message, transposition,
         fillcolumnwise, emptycolumnwise, fitness):
         fillcolumnwise, emptycolumnwise, fitness):
-    plaintext = column_transposition_decipher(message, transposition, 
+    plaintext = column_transposition_decipher(message, transposition,
         fillcolumnwise=fillcolumnwise, emptycolumnwise=emptycolumnwise)
     fit = fitness(sanitise(plaintext))
     logger.debug('Column transposition break attempt using key {0} '
         fillcolumnwise=fillcolumnwise, emptycolumnwise=emptycolumnwise)
     fit = fitness(sanitise(plaintext))
     logger.debug('Column transposition break attempt using key {0} '
@@ -296,8 +300,8 @@ def scytale_break_mp(message, max_key_length=20,
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
-             first entering a neighbourhood, this truth is so well fixed in the \
-             minds of the surrounding families, that he is considered the \
+             first entering a neighbourhood, this truth is so well fixed in \
+             the minds of the surrounding families, that he is considered the \
              rightful property of some one or other of their daughters."), \
         5)) # doctest: +ELLIPSIS
     (5, -709.4646722...)
              rightful property of some one or other of their daughters."), \
         5)) # doctest: +ELLIPSIS
     (5, -709.4646722...)
@@ -305,31 +309,30 @@ def scytale_break_mp(message, max_key_length=20,
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
             "It is a truth universally acknowledged, that a single man in \
              possession of a good fortune, must be in want of a wife. However \
              little known the feelings or views of such a man may be on his \
-             first entering a neighbourhood, this truth is so well fixed in the \
-             minds of the surrounding families, that he is considered the \
+             first entering a neighbourhood, this truth is so well fixed in \
+             the minds of the surrounding families, that he is considered the \
              rightful property of some one or other of their daughters."), \
         5), \
         fitness=Ptrigrams) # doctest: +ELLIPSIS
     (5, -997.0129085...)
     """
     with Pool() as pool:
              rightful property of some one or other of their daughters."), \
         5), \
         fitness=Ptrigrams) # doctest: +ELLIPSIS
     (5, -997.0129085...)
     """
     with Pool() as pool:
-        helper_args = [(message, trans, False, True, fitness) 
-            for trans in 
-                [[col for col in range(math.ceil(len(message)/rows))] 
+        helper_args = [(message, trans, False, True, fitness)
+            for trans in
+                [[col for col in range(math.ceil(len(message)/rows))]
                     for rows in range(1,max_key_length+1)]]
                     for rows in range(1,max_key_length+1)]]
-        # Gotcha: the helper function here needs to be defined at the top level 
+        # Gotcha: the helper function here needs to be defined at the top level
         #   (limitation of Pool.starmap)
         #   (limitation of Pool.starmap)
-        breaks = pool.starmap(column_transposition_break_worker, 
-          helper_args, chunksize) 
-        best = max(breaks, key=lambda k: k[1]) 
+        breaks = pool.starmap(column_transposition_break_worker,
+                              helper_args, chunksize)
+        best = max(breaks, key=lambda k: k[1])
         return math.trunc(len(message) / len(best[0][0])), best[1]
 scytale_break = scytale_break_mp
 
 
         return math.trunc(len(message) / len(best[0][0])), best[1]
 scytale_break = scytale_break_mp
 
 
-def vigenere_keyword_break_mp(message, wordlist=keywords, fitness=Pletters, 
-                     chunksize=500):
-    """Breaks a vigenere cipher using a dictionary and 
-    frequency analysis
+def vigenere_keyword_break_mp(message, wordlist=keywords, fitness=Pletters,
+                              chunksize=500):
+    """Breaks a vigenere cipher using a dictionary and frequency analysis.
 
     >>> vigenere_keyword_break_mp(vigenere_encipher(sanitise('this is a test ' \
              'message for the vigenere decipherment'), 'cat'), \
 
     >>> vigenere_keyword_break_mp(vigenere_encipher(sanitise('this is a test ' \
              'message for the vigenere decipherment'), 'cat'), \
@@ -337,11 +340,12 @@ def vigenere_keyword_break_mp(message, wordlist=keywords, fitness=Pletters,
     ('cat', -52.947271216...)
     """
     with Pool() as pool:
     ('cat', -52.947271216...)
     """
     with Pool() as pool:
-        helper_args = [(message, word, fitness) 
+        helper_args = [(message, word, fitness)
                        for word in wordlist]
                        for word in wordlist]
-        # Gotcha: the helper function here needs to be defined at the top level 
+        # Gotcha: the helper function here needs to be defined at the top level
         #   (limitation of Pool.starmap)
         #   (limitation of Pool.starmap)
-        breaks = pool.starmap(vigenere_keyword_break_worker, helper_args, chunksize) 
+        breaks = pool.starmap(vigenere_keyword_break_worker, helper_args,
+                              chunksize)
         return max(breaks, key=lambda k: k[1])
 vigenere_keyword_break = vigenere_keyword_break_mp
 
         return max(breaks, key=lambda k: k[1])
 vigenere_keyword_break = vigenere_keyword_break_mp
 
@@ -349,7 +353,7 @@ def vigenere_keyword_break_worker(message, keyword, fitness):
     plaintext = vigenere_decipher(message, keyword)
     fit = fitness(plaintext)
     logger.debug('Vigenere keyword break attempt using key {0} gives fit of '
     plaintext = vigenere_decipher(message, keyword)
     fit = fitness(plaintext)
     logger.debug('Vigenere keyword break attempt using key {0} gives fit of '
-                 '{1} and decrypt starting: {2}'.format(keyword, 
+                 '{1} and decrypt starting: {2}'.format(keyword,
                      fit, sanitise(plaintext)[:50]))
     return keyword, fit
 
                      fit, sanitise(plaintext)[:50]))
     return keyword, fit
 
@@ -374,8 +378,8 @@ def vigenere_frequency_break(message, max_key_length=20, fitness=Pletters):
         fit = fitness(plaintext)
         return key, fit
     sanitised_message = sanitise(message)
         fit = fitness(plaintext)
         return key, fit
     sanitised_message = sanitise(message)
-    results = starmap(worker, [(sanitised_message, i, fitness) 
-        for i in range(1, max_key_length+1)])
+    results = starmap(worker, [(sanitised_message, i, fitness)
+                               for i in range(1, max_key_length+1)])
     return max(results, key=lambda k: k[1])
 
 
     return max(results, key=lambda k: k[1])
 
 
@@ -393,13 +397,14 @@ def beaufort_frequency_break(message, max_key_length=20, fitness=Pletters):
     """
     def worker(message, key_length, fitness):
         splits = every_nth(sanitised_message, key_length)
     """
     def worker(message, key_length, fitness):
         splits = every_nth(sanitised_message, key_length)
-        key = ''.join([chr(-caesar_break(s)[0] % 26 + ord('a')) for s in splits])
+        key = ''.join([chr(-caesar_break(s)[0] % 26 + ord('a'))
+                       for s in splits])
         plaintext = beaufort_decipher(message, key)
         fit = fitness(plaintext)
         return key, fit
     sanitised_message = sanitise(message)
         plaintext = beaufort_decipher(message, key)
         fit = fitness(plaintext)
         return key, fit
     sanitised_message = sanitise(message)
-    results = starmap(worker, [(sanitised_message, i, fitness) 
-        for i in range(1, max_key_length+1)])
+    results = starmap(worker, [(sanitised_message, i, fitness)
+                               for i in range(1, max_key_length+1)])
     return max(results, key=lambda k: k[1])
 
 
     return max(results, key=lambda k: k[1])
 
 
index babbea19ceed80bd9d95fee347c64e53256b5626..62219efe54ab2ad20e07c1838d5ade29e6511d7d 100644 (file)
@@ -96,10 +96,10 @@ def ngrams(text, n):
     """Returns all n-grams of a text
     
     >>> ngrams(sanitise('the quick brown fox'), 2) # doctest: +NORMALIZE_WHITESPACE
     """Returns all n-grams of a text
     
     >>> ngrams(sanitise('the quick brown fox'), 2) # doctest: +NORMALIZE_WHITESPACE
-    ['th', 'he', 'eq', 'qu', 'ui', 'ic', 'ck', 'kb', 'br', 'ro', 'ow', 'wn', 
+    ['th', 'he', 'eq', 'qu', 'ui', 'ic', 'ck', 'kb', 'br', 'ro', 'ow', 'wn',
      'nf', 'fo', 'ox']
     >>> ngrams(sanitise('the quick brown fox'), 4) # doctest: +NORMALIZE_WHITESPACE
      'nf', 'fo', 'ox']
     >>> ngrams(sanitise('the quick brown fox'), 4) # doctest: +NORMALIZE_WHITESPACE
-    ['theq', 'hequ', 'equi', 'quic', 'uick', 'ickb', 'ckbr', 'kbro', 'brow', 
+    ['theq', 'hequ', 'equi', 'quic', 'uick', 'ickb', 'ckbr', 'kbro', 'brow',
      'rown', 'ownf', 'wnfo', 'nfox']
     """
     return [text[i:i+n] for i in range(len(text)-n+1)]
      'rown', 'ownf', 'wnfo', 'nfox']
     """
     return [text[i:i+n] for i in range(len(text)-n+1)]
@@ -129,30 +129,29 @@ Pl = Pdist(datafile('count_1l.txt'), lambda _k, _N: 0)
 P2l = Pdist(datafile('count_2l.txt'), lambda _k, _N: 0)
 P3l = Pdist(datafile('count_3l.txt'), lambda _k, _N: 0)
 
 P2l = Pdist(datafile('count_2l.txt'), lambda _k, _N: 0)
 P3l = Pdist(datafile('count_3l.txt'), lambda _k, _N: 0)
 
-def Pwords(words): 
+def Pwords(words):
     """The Naive Bayes log probability of a sequence of words.
     """
     return sum(Pw[w.lower()] for w in words)
 
     """The Naive Bayes log probability of a sequence of words.
     """
     return sum(Pw[w.lower()] for w in words)
 
-def Pwords_wrong(words): 
+def Pwords_wrong(words):
     """The Naive Bayes log probability of a sequence of words.
     """
     return sum(Pw_wrong[w.lower()] for w in words)
 
     """The Naive Bayes log probability of a sequence of words.
     """
     return sum(Pw_wrong[w.lower()] for w in words)
 
-
 def Pletters(letters):
     """The Naive Bayes log probability of a sequence of letters.
     """
     return sum(Pl[l.lower()] for l in letters)
 
 def Pbigrams(letters):
 def Pletters(letters):
     """The Naive Bayes log probability of a sequence of letters.
     """
     return sum(Pl[l.lower()] for l in letters)
 
 def Pbigrams(letters):
-    """The Naive Bayes log probability of the bigrams formed from a sequence 
+    """The Naive Bayes log probability of the bigrams formed from a sequence
     of letters.
     """
     return sum(P2l[p] for p in ngrams(letters, 2))
 
 def Ptrigrams(letters):
     of letters.
     """
     return sum(P2l[p] for p in ngrams(letters, 2))
 
 def Ptrigrams(letters):
-    """The Naive Bayes log probability of the trigrams formed from a sequence 
+    """The Naive Bayes log probability of the trigrams formed from a sequence
     of letters.
     """
     return sum(P3l[p] for p in ngrams(letters, 3))
     of letters.
     """
     return sum(P3l[p] for p in ngrams(letters, 3))
@@ -165,8 +164,8 @@ def cosine_similarity_score(text):
     >>> cosine_similarity_score('abcabc') # doctest: +ELLIPSIS
     0.26228882...
     """
     >>> cosine_similarity_score('abcabc') # doctest: +ELLIPSIS
     0.26228882...
     """
-    return norms.cosine_similarity(english_counts, 
-        collections.Counter(sanitise(text)))
+    return norms.cosine_similarity(english_counts,
+                                   collections.Counter(sanitise(text)))
 
 
 if __name__ == "__main__":
 
 
 if __name__ == "__main__":
index b8e4bf1ef82c8b8ea6f85c0e3c3597d90b7d9b8b..36e7fa4440b41942386e550b4403d2bf7d59e058 100644 (file)
--- a/norms.py
+++ b/norms.py
@@ -1,9 +1,10 @@
+"""Define a variety of norms for finding distances between vectors"""
+
 import collections
 import collections
-from math import log10
 
 def normalise(frequencies):
     """Scale a set of frequencies so they sum to one
 
 def normalise(frequencies):
     """Scale a set of frequencies so they sum to one
-    
+
     >>> sorted(normalise({1: 1, 2: 0}).items())
     [(1, 1.0), (2, 0.0)]
     >>> sorted(normalise({1: 1, 2: 1}).items())
     >>> sorted(normalise({1: 1, 2: 0}).items())
     [(1, 1.0), (2, 0.0)]
     >>> sorted(normalise({1: 1, 2: 1}).items())
@@ -14,12 +15,12 @@ def normalise(frequencies):
     [(1, 0.25), (2, 0.5), (3, 0.25)]
     """
     length = sum([f for f in frequencies.values()])
     [(1, 0.25), (2, 0.5), (3, 0.25)]
     """
     length = sum([f for f in frequencies.values()])
-    return collections.defaultdict(int, ((k, v / length) 
+    return collections.defaultdict(int, ((k, v / length)
         for (k, v) in frequencies.items()))
 
 def euclidean_scale(frequencies):
     """Scale a set of frequencies so they have a unit euclidean length
         for (k, v) in frequencies.items()))
 
 def euclidean_scale(frequencies):
     """Scale a set of frequencies so they have a unit euclidean length
-    
+
     >>> sorted(euclidean_scale({1: 1, 2: 0}).items())
     [(1, 1.0), (2, 0.0)]
     >>> sorted(euclidean_scale({1: 1, 2: 1}).items()) # doctest: +ELLIPSIS
     >>> sorted(euclidean_scale({1: 1, 2: 0}).items())
     [(1, 1.0), (2, 0.0)]
     >>> sorted(euclidean_scale({1: 1, 2: 1}).items()) # doctest: +ELLIPSIS
@@ -30,17 +31,21 @@ def euclidean_scale(frequencies):
     [(1, 0.408248...), (2, 0.81649658...), (3, 0.408248...)]
     """
     length = sum([f ** 2 for f in frequencies.values()]) ** 0.5
     [(1, 0.408248...), (2, 0.81649658...), (3, 0.408248...)]
     """
     length = sum([f ** 2 for f in frequencies.values()]) ** 0.5
-    return collections.defaultdict(int, ((k, v / length) 
+    return collections.defaultdict(int, ((k, v / length)
         for (k, v) in frequencies.items()))
 
 def identity_scale(frequencies):
         for (k, v) in frequencies.items()))
 
 def identity_scale(frequencies):
+    """Don't scale a set of frequencies. (For use when a function expects a
+    scaling function but you don't want to supply one.)
+    """
     return frequencies
         
 
 def l2(frequencies1, frequencies2):
     return frequencies
         
 
 def l2(frequencies1, frequencies2):
-    """Finds the distances between two frequency profiles, expressed as dictionaries.
+    """Finds the distances between two frequency profiles, expressed as
+    dictionaries.
     Assumes every key in frequencies1 is also in frequencies2
     Assumes every key in frequencies1 is also in frequencies2
-    
+
     >>> l2({'a':1, 'b':1, 'c':1}, {'a':1, 'b':1, 'c':1})
     0.0
     >>> l2({'a':2, 'b':2, 'c':2}, {'a':1, 'b':1, 'c':1}) # doctest: +ELLIPSIS
     >>> l2({'a':1, 'b':1, 'c':1}, {'a':1, 'b':1, 'c':1})
     0.0
     >>> l2({'a':2, 'b':2, 'c':2}, {'a':1, 'b':1, 'c':1}) # doctest: +ELLIPSIS
@@ -62,7 +67,7 @@ def l2(frequencies1, frequencies2):
 euclidean_distance = l2
 
 def l1(frequencies1, frequencies2):
 euclidean_distance = l2
 
 def l1(frequencies1, frequencies2):
-    """Finds the distances between two frequency profiles, expressed as 
+    """Finds the distances between two frequency profiles, expressed as
     dictionaries. Assumes every key in frequencies1 is also in frequencies2
 
     >>> l1({'a':1, 'b':1, 'c':1}, {'a':1, 'b':1, 'c':1})
     dictionaries. Assumes every key in frequencies1 is also in frequencies2
 
     >>> l1({'a':1, 'b':1, 'c':1}, {'a':1, 'b':1, 'c':1})
@@ -82,7 +87,7 @@ def l1(frequencies1, frequencies2):
     return total
 
 def l3(frequencies1, frequencies2):
     return total
 
 def l3(frequencies1, frequencies2):
-    """Finds the distances between two frequency profiles, expressed as 
+    """Finds the distances between two frequency profiles, expressed as
     dictionaries. Assumes every key in frequencies1 is also in frequencies2
 
     >>> l3({'a':1, 'b':1, 'c':1}, {'a':1, 'b':1, 'c':1})
     dictionaries. Assumes every key in frequencies1 is also in frequencies2
 
     >>> l3({'a':1, 'b':1, 'c':1}, {'a':1, 'b':1, 'c':1})
@@ -105,10 +110,10 @@ def l3(frequencies1, frequencies2):
     return total ** (1/3)
 
 def geometric_mean(frequencies1, frequencies2):
     return total ** (1/3)
 
 def geometric_mean(frequencies1, frequencies2):
-    """Finds the geometric mean of the absolute differences between two frequency profiles, 
-    expressed as dictionaries.
+    """Finds the geometric mean of the absolute differences between two
+    frequency profiles, expressed as dictionaries.
     Assumes every key in frequencies1 is also in frequencies2
     Assumes every key in frequencies1 is also in frequencies2
-    
+
     >>> geometric_mean({'a':2, 'b':2, 'c':2}, {'a':1, 'b':1, 'c':1})
     1
     >>> geometric_mean({'a':2, 'b':2, 'c':2}, {'a':1, 'b':1, 'c':1})
     >>> geometric_mean({'a':2, 'b':2, 'c':2}, {'a':1, 'b':1, 'c':1})
     1
     >>> geometric_mean({'a':2, 'b':2, 'c':2}, {'a':1, 'b':1, 'c':1})
@@ -131,8 +136,8 @@ def geometric_mean(frequencies1, frequencies2):
     return total
 
 def harmonic_mean(frequencies1, frequencies2):
     return total
 
 def harmonic_mean(frequencies1, frequencies2):
-    """Finds the harmonic mean of the absolute differences between two frequency profiles, 
-    expressed as dictionaries.
+    """Finds the harmonic mean of the absolute differences between two
+    frequency profiles, expressed as dictionaries.
     Assumes every key in frequencies1 is also in frequencies2
 
     >>> harmonic_mean({'a':2, 'b':2, 'c':2}, {'a':1, 'b':1, 'c':1})
     Assumes every key in frequencies1 is also in frequencies2
 
     >>> harmonic_mean({'a':2, 'b':2, 'c':2}, {'a':1, 'b':1, 'c':1})
@@ -160,7 +165,8 @@ def harmonic_mean(frequencies1, frequencies2):
 
 
 def cosine_similarity(frequencies1, frequencies2):
 
 
 def cosine_similarity(frequencies1, frequencies2):
-    """Finds the distances between two frequency profiles, expressed as dictionaries.
+    """Finds the distances between two frequency profiles, expressed as 
+    dictionaries.
     Assumes every key in frequencies1 is also in frequencies2
 
     >>> cosine_similarity({'a':1, 'b':1, 'c':1}, {'a':1, 'b':1, 'c':1}) # doctest: +ELLIPSIS
     Assumes every key in frequencies1 is also in frequencies2
 
     >>> cosine_similarity({'a':1, 'b':1, 'c':1}, {'a':1, 'b':1, 'c':1}) # doctest: +ELLIPSIS
index 1af1b62fc8eb3270c35c4bb39a773804faf8da47..a64ea5d4eb4248edaaff12c791df088f8109e21c 100644 (file)
@@ -1,3 +1,5 @@
+"""Segment a collection of letters into words"""
+
 import language_models
 import sys
 from functools import lru_cache
 import language_models
 import sys
 from functools import lru_cache
@@ -8,7 +10,7 @@ def segment(text):
     """Return a list of words that is the best segmentation of text.
     """
     if not text: return []
     """Return a list of words that is the best segmentation of text.
     """
     if not text: return []
-    candidates = ([first]+segment(rest) for first,rest in splits(text))
+    candidates = ([first]+segment(rest) for first, rest in splits(text))
     return max(candidates, key=language_models.Pwords)
 
 @lru_cache()
     return max(candidates, key=language_models.Pwords)
 
 @lru_cache()
@@ -16,13 +18,13 @@ def segment_wrong(text):
     """Return a list of words that is the best segmentation of text.
     """
     if not text: return []
     """Return a list of words that is the best segmentation of text.
     """
     if not text: return []
-    candidates = ([first]+segment(rest) for first,rest in splits(text))
+    candidates = ([first]+segment(rest) for first, rest in splits(text))
     return max(candidates, key=language_models.Pwords_wrong)
 
 
 def splits(text, L=20):
     """Return a list of all possible (first, rest) pairs, len(first)<=L.
     """
     return max(candidates, key=language_models.Pwords_wrong)
 
 
 def splits(text, L=20):
     """Return a list of all possible (first, rest) pairs, len(first)<=L.
     """
-    return [(text[:i+1], text[i+1:]) 
+    return [(text[:i+1], text[i+1:])
             for i in range(min(len(text), L))]
 
             for i in range(min(len(text), L))]