Source code for src.ch04.practice.p2_identify_cipher
"""Identify letter transposition or substitution cipher."""
from collections import Counter
[docs]def identify_cipher(ciphertext: str, threshold: float) -> bool:
    """Identify letter transposition or substitution cipher.
    Compare most frequent letters in **ciphertext** with the most frequent
    letters in the English alphabet. If above **threshold**, it is a letter
    transposition cipher. If not, it is a letter substitution cipher.
    Args:
        ciphertext (str): Encrypted message to identify.
        threshold (float): Percent match in decimal form.
    Returns:
        :py:obj:`True` if the **ciphertext** is a letter transposition cipher.
        :py:obj:`False` otherwise.
    """
    most_freq = 'etaoinshrdlu'
    # Convert most frequent English letters into a Counter.
    english_freq = Counter(most_freq)
    # Identify most frequent letters in ciphertext and convert into Counter.
    ciphertext_freq = Counter([i[0] for i in
                               Counter(ciphertext.replace(' ', ''))
                               .most_common(len(most_freq))])
    # Find letters that they have in common.
    intersection = english_freq & ciphertext_freq
    # Count letters they had in common.
    count = len(intersection.keys())
    if count / len(most_freq) >= threshold:
        return True
    return False 
[docs]def is_transposition(ciphertext: str) -> bool:
    """Identify letter transposition cipher.
    Wrapper for :func:`identify_cipher`. **threshold** defaults to ``0.75``.
    Args:
        ciphertext (str): Encrypted message to identify.
    Returns:
        :py:obj:`True` if the **ciphertext** is a letter transposition cipher.
        :py:obj:`False` otherwise.
    """
    return identify_cipher(ciphertext, 0.75) 
[docs]def is_substitution(ciphertext: str) -> bool:
    """Identify letter substitution cipher.
    Wrapper for :func:`identify_cipher`. **threshold** defaults to ``0.45``.
    Args:
        ciphertext (str): Encrypted message to identify.
    Returns:
        :py:obj:`True` if the **ciphertext** is a letter substitution cipher.
        :py:obj:`False` otherwise.
    """
    return not identify_cipher(ciphertext, 0.45) 
[docs]def main(ciphertext: str = None) -> None:
    """Demonstrate the cipher identifier.
    This is only supposed to be a demo, but coverage necessitates
    excessiveness.
    Args:
        ciphertext (str): Encrypted letter transposition or letter
            substitution cipher to demonstrate.
    Returns:
        :py:obj:`None`. Identifies **ciphertext**'s cipher.
    """
    print('I can tell the difference between a letter transposition cipher '
          'and a letter\nsubstitution cipher - like those used in decoder '
          'rings. Sorry-not-sorry that\nyou collected all those box tops.\n')
    if ciphertext is None:
        # Used key of XCTJYGPIUWMQBDESOLKZNHFRVA in Al Sweigart's
        # Cracking Codes with Python simpleSubCipher.py
        ciphertext = 'ziy yxpqy ixk qxdjyj cnz ziy dykz uk ybszv'
    print(f'Testing cipher: {ciphertext}\n')
    if is_substitution(ciphertext):
        print('I hereby decree that this is a pitiable attempt at a '
              'substitution cipher.\n')
    else:
        print('Hmm, I declare this is a pathetic attempt at a transposition '
              'cipher.\n') 
if __name__ == '__main__':
    main()