# Path: lantern/score.py
# def score(text, *score_functions):
#     """Score ``text`` using ``score_functions``.
# 
#     Examples:
#         >>> score("abc", function_a)
#         >>> score("abc", function_a, function_b)
# 
#     Args:
#         text (str): The text to score
#         *score_functions (variable length argument list): functions to score with
# 
#     Returns:
#         Arithmetic mean of scores
# 
#     Raises:
#         ValueError: If score_functions is empty
#     """
#     if not score_functions:
#         raise ValueError("score_functions must not be empty")
# 
#     return statistics.mean(func(text) for func in score_functions)
# 
# Path: lantern/modules/shift.py
# def make_shift_function(alphabet: Iterable, operator: ShiftOperator = subtract) -> ShiftFunction:
#     def shift_case_sensitive(shift, symbol):
# def crack(ciphertext: Iterable, *fitness_functions: Iterable,
#           min_key: int = 0, max_key: int = 26,
#           shift_function: ShiftFunction = shift_decrypt_case_english) -> List[Decryption]:
# def decrypt(key: int, ciphertext: Iterable, shift_function: ShiftFunction = shift_decrypt_case_english) -> Iterable:
# def encrypt(key: int, plaintext: Iterable, shift_function: ShiftFunction = shift_encrypt_case_english) -> Iterable:
# 
# Path: lantern/structures/decryption.py
# class Decryption:
#     """A decryption object, composed of plaintext, a score and the key.
# 
#     Example:
#         >>> decryption = Decryption("example", "key", -10)
#         >>> decryption.plaintext
#         example
#         >>> decryption.key
#         key
#         >>> decryption.score
#         -10
#     """
# 
#     def __init__(self, plaintext, key, score):
#         """
#         Args:
#             plaintext: The decrypted ciphertext
#             key: The key which resulted in this decryption
#             score: The score of this decryption
#         """
#         self.plaintext = plaintext
#         self.key = key
#         self.score = score
# 
#     def __lt__(self, other):
#         """Compare decryptions with other decryptions by score.
# 
#         Args:
#             other: Object to compare with
# 
#         Returns:
#             True if self is less than other, else False
#         """
#         return self.score < other.score
# 
# Path: lantern/analysis/frequency.py
# def index_of_coincidence(*texts):
#     """Calculate the index of coincidence for one or more ``texts``.
#     The results are averaged over multiple texts to return the delta index of coincidence.
# 
#     Examples:
#         >>> index_of_coincidence("aabbc")
#         0.2
# 
#         >>> index_of_coincidence("aabbc", "abbcc")
#         0.2
# 
#     Args:
#         *texts (variable length argument list): The texts to analyze
# 
#     Returns:
#         Decimal value of the index of coincidence
# 
#     Raises:
#         ValueError: If texts is empty
#         ValueError: If any text is less that 2 character long
#     """
#     if not texts:
#         raise ValueError("texts must not be empty")
# 
#     return statistics.mean(_calculate_index_of_coincidence(frequency_analyze(text), len(text)) for text in texts)
# 
# ENGLISH_IC = _calculate_index_of_coincidence(english.unigrams, sum(english.unigrams.values()))
# 
# Path: lantern/util.py
# def split_columns(text, n_columns):
#     """Split ``text`` into ``n_columns`` many columns.
# 
#     Example:
#         >>> split_columns("example", 2)
#         ['eape', 'xml']
# 
#     Args:
#         text (str): The text to split
#         n_columns (int): The number of columns to create
# 
#     Returns:
#         List of columns
# 
#     Raises:
#         ValueError: If n_cols is <= 0 or >= len(text)
#     """
#     if n_columns <= 0 or n_columns > len(text):
#         raise ValueError("n_columns must be within the bounds of 1 and text length")
# 
#     return [text[i::n_columns] for i in range(n_columns)]
# 
# def remove(text, exclude):
#     """Remove ``exclude`` symbols from ``text``.
# 
#     Example:
#         >>> remove("example text", string.whitespace)
#         'exampletext'
# 
#     Args:
#         text (str): The text to modify
#         exclude (iterable): The symbols to exclude
# 
#     Returns:
#         ``text`` with ``exclude`` symbols removed
#     """
#     exclude = ''.join(str(symbol) for symbol in exclude)
#     return text.translate(str.maketrans('', '', exclude))


import string
from lantern import score
from lantern.modules import shift
from lantern.structures import Decryption
from lantern.analysis.frequency import index_of_coincidence, ENGLISH_IC
from lantern.util import split_columns, remove
        plaintext = decrypt(key, original_text)
        period_decryptions.append(Decryption(plaintext, key, score(plaintext, *fitness_functions)))

    return sorted(period_decryptions, reverse=True)


# Name should be different?, say youre finding key periods through IC.
def key_periods(ciphertext, max_key_period):
    """Rank all key periods for ``ciphertext`` up to and including ``max_key_period``

    Example:
        >>> key_periods(ciphertext, 30)
        [2, 4, 8, 3, ...]

    Args:
        ciphertext (str): The text to analyze
        max_key_period (int): The maximum period the key could be

    Returns:
        Sorted list of keys

    Raises:
        ValueError: If max_key_period is less than or equal to 0
    """
    if max_key_period <= 0:
        raise ValueError("max_key_period must be a positive integer")

    key_scores = []
    for period in range(1, min(max_key_period, len(ciphertext)) + 1):
        score = abs(ENGLISH_IC - index_of_coincidence(*split_columns(ciphertext, period)))

# Path: lantern/score.py
# def score(text, *score_functions):
#     """Score ``text`` using ``score_functions``.
# 
#     Examples:
#         >>> score("abc", function_a)
#         >>> score("abc", function_a, function_b)
# 
#     Args:
#         text (str): The text to score
#         *score_functions (variable length argument list): functions to score with
# 
#     Returns:
#         Arithmetic mean of scores
# 
#     Raises:
#         ValueError: If score_functions is empty
#     """
#     if not score_functions:
#         raise ValueError("score_functions must not be empty")
# 
#     return statistics.mean(func(text) for func in score_functions)
# 
# Path: lantern/modules/shift.py
# def make_shift_function(alphabet: Iterable, operator: ShiftOperator = subtract) -> ShiftFunction:
#     def shift_case_sensitive(shift, symbol):
# def crack(ciphertext: Iterable, *fitness_functions: Iterable,
#           min_key: int = 0, max_key: int = 26,
#           shift_function: ShiftFunction = shift_decrypt_case_english) -> List[Decryption]:
# def decrypt(key: int, ciphertext: Iterable, shift_function: ShiftFunction = shift_decrypt_case_english) -> Iterable:
# def encrypt(key: int, plaintext: Iterable, shift_function: ShiftFunction = shift_encrypt_case_english) -> Iterable:
# 
# Path: lantern/structures/decryption.py
# class Decryption:
#     """A decryption object, composed of plaintext, a score and the key.
# 
#     Example:
#         >>> decryption = Decryption("example", "key", -10)
#         >>> decryption.plaintext
#         example
#         >>> decryption.key
#         key
#         >>> decryption.score
#         -10
#     """
# 
#     def __init__(self, plaintext, key, score):
#         """
#         Args:
#             plaintext: The decrypted ciphertext
#             key: The key which resulted in this decryption
#             score: The score of this decryption
#         """
#         self.plaintext = plaintext
#         self.key = key
#         self.score = score
# 
#     def __lt__(self, other):
#         """Compare decryptions with other decryptions by score.
# 
#         Args:
#             other: Object to compare with
# 
#         Returns:
#             True if self is less than other, else False
#         """
#         return self.score < other.score
# 
# Path: lantern/analysis/frequency.py
# def index_of_coincidence(*texts):
#     """Calculate the index of coincidence for one or more ``texts``.
#     The results are averaged over multiple texts to return the delta index of coincidence.
# 
#     Examples:
#         >>> index_of_coincidence("aabbc")
#         0.2
# 
#         >>> index_of_coincidence("aabbc", "abbcc")
#         0.2
# 
#     Args:
#         *texts (variable length argument list): The texts to analyze
# 
#     Returns:
#         Decimal value of the index of coincidence
# 
#     Raises:
#         ValueError: If texts is empty
#         ValueError: If any text is less that 2 character long
#     """
#     if not texts:
#         raise ValueError("texts must not be empty")
# 
#     return statistics.mean(_calculate_index_of_coincidence(frequency_analyze(text), len(text)) for text in texts)
# 
# ENGLISH_IC = _calculate_index_of_coincidence(english.unigrams, sum(english.unigrams.values()))
# 
# Path: lantern/util.py
# def split_columns(text, n_columns):
#     """Split ``text`` into ``n_columns`` many columns.
# 
#     Example:
#         >>> split_columns("example", 2)
#         ['eape', 'xml']
# 
#     Args:
#         text (str): The text to split
#         n_columns (int): The number of columns to create
# 
#     Returns:
#         List of columns
# 
#     Raises:
#         ValueError: If n_cols is <= 0 or >= len(text)
#     """
#     if n_columns <= 0 or n_columns > len(text):
#         raise ValueError("n_columns must be within the bounds of 1 and text length")
# 
#     return [text[i::n_columns] for i in range(n_columns)]
# 
# def remove(text, exclude):
#     """Remove ``exclude`` symbols from ``text``.
# 
#     Example:
#         >>> remove("example text", string.whitespace)
#         'exampletext'
# 
#     Args:
#         text (str): The text to modify
#         exclude (iterable): The symbols to exclude
# 
#     Returns:
#         ``text`` with ``exclude`` symbols removed
#     """
#     exclude = ''.join(str(symbol) for symbol in exclude)
#     return text.translate(str.maketrans('', '', exclude))


# Path: lantern/modules/vigenere.py
import string
from lantern import score
from lantern.modules import shift
from lantern.structures import Decryption
from lantern.analysis.frequency import index_of_coincidence, ENGLISH_IC
from lantern.util import split_columns, remove

        plaintext = decrypt(key, original_text)
        period_decryptions.append(Decryption(plaintext, key, score(plaintext, *fitness_functions)))

    return sorted(period_decryptions, reverse=True)


# Name should be different?, say youre finding key periods through IC.
def key_periods(ciphertext, max_key_period):
    """Rank all key periods for ``ciphertext`` up to and including ``max_key_period``

    Example:
        >>> key_periods(ciphertext, 30)
        [2, 4, 8, 3, ...]

    Args:
        ciphertext (str): The text to analyze
        max_key_period (int): The maximum period the key could be

    Returns:
        Sorted list of keys

    Raises:
        ValueError: If max_key_period is less than or equal to 0
    """
    if max_key_period <= 0:
        raise ValueError("max_key_period must be a positive integer")

    key_scores = []
    for period in range(1, min(max_key_period, len(ciphertext)) + 1):
        score = abs(ENGLISH_IC - index_of_coincidence(*split_columns(ciphertext, period)))

        key_scores.append((period, score))