# Path: preimage/utils/alphabet.py
# def get_index_to_n_gram(alphabet, n):
#     n_grams = get_n_grams(alphabet, n)
#     indexes = numpy.arange(len(n_grams))
#     index_to_n_gram = dict(zip(indexes, n_grams))
#     return index_to_n_gram
# 
# Path: preimage/exceptions/n_gram.py
# class InvalidNGramLengthError(ValueError):
#     def __init__(self, n, min_n=0):
#         self.n = n
#         self.min_n = min_n
# 
#     def __str__(self):
#         error_message = 'n must be greater than {:d}. Got: n={:d}'.format(self.min_n, self.n)
#         return error_message
# 
# class InvalidYLengthError(ValueError):
#     def __init__(self, n, y_length):
#         self.n = n
#         self.y_length = y_length
# 
#     def __str__(self):
#         error_message = 'y_length must be >= n. Got: y_length={:d}, n={:d}'.format(self.y_length, self.n)
#         return error_message
# 
# class NoThresholdsError(ValueError):
#     def __str__(self):
#         error_message = 'thresholds must be provided when y_length is None'
#         return error_message
# 
# Path: preimage/exceptions/shape.py
# class InvalidShapeError(ValueError):
#     def __init__(self, parameter_name, parameter_shape, valid_shapes):
#         self.parameter_name = parameter_name
#         self.parameter_shape = parameter_shape
#         self.valid_shapes = [str(valid_shape) for valid_shape in valid_shapes]
# 
#     def __str__(self):
#         valid_shapes_string = ' or '.join(self.valid_shapes)
#         error_message = "{} wrong shape: Expected: {} Got: {}".format(self.parameter_name, valid_shapes_string,
#                                                                       str(self.parameter_shape))
#         return error_message


from copy import deepcopy
from preimage.utils.alphabet import get_index_to_n_gram
from preimage.exceptions.n_gram import InvalidNGramLengthError, InvalidYLengthError, NoThresholdsError
from preimage.exceptions.shape import InvalidShapeError
import numpy
    merge the paths together when the graph is not connected. We chose to find the longest path and merge the remaining
    paths after, but this might differ from what Cortes et al. did in their work. The n-gram kernel also has a
    non-unique pre-image. For instance if the string to predict has the n-grams "ab" and "ba" both "aba" and "bab" are
    valid pre-image. In that case we chose randomly a pre-image among the possible ones (this is done by visiting the
    edges and the nodes in a random order). Note that the code could also be faster but we only cared about implementing
    the algorithm correctly.

    References
    ----------
    .. [1] Cortes, Corinna, Mehryar Mohri, and Jason Weston. "A general regression framework for learning
       string-to-string mappings." Predicting Structured Data (2007): 143-168.
    """

    def __init__(self, alphabet, n, min_length=1, is_merging_path=True):
        """Initialize the eulerian path algorithm

        Parameters
        ----------
        alphabet : list
            list of letters.
        n : int
            n-gram length.
        min_length :int
            Minimum length of the predicted string.
        is_merging_path : bool
            True if merges path when the graph is not connected, False otherwise (choose the longest path instead).
        """
        self.n = int(n)
        self.is_merging_path = is_merging_path
        self.min_n_gram_count = 1 if min_length <= self.n else min_length - self.n + 1

# Path: preimage/utils/alphabet.py
# def get_index_to_n_gram(alphabet, n):
#     n_grams = get_n_grams(alphabet, n)
#     indexes = numpy.arange(len(n_grams))
#     index_to_n_gram = dict(zip(indexes, n_grams))
#     return index_to_n_gram
# 
# Path: preimage/exceptions/n_gram.py
# class InvalidNGramLengthError(ValueError):
#     def __init__(self, n, min_n=0):
#         self.n = n
#         self.min_n = min_n
# 
#     def __str__(self):
#         error_message = 'n must be greater than {:d}. Got: n={:d}'.format(self.min_n, self.n)
#         return error_message
# 
# class InvalidYLengthError(ValueError):
#     def __init__(self, n, y_length):
#         self.n = n
#         self.y_length = y_length
# 
#     def __str__(self):
#         error_message = 'y_length must be >= n. Got: y_length={:d}, n={:d}'.format(self.y_length, self.n)
#         return error_message
# 
# class NoThresholdsError(ValueError):
#     def __str__(self):
#         error_message = 'thresholds must be provided when y_length is None'
#         return error_message
# 
# Path: preimage/exceptions/shape.py
# class InvalidShapeError(ValueError):
#     def __init__(self, parameter_name, parameter_shape, valid_shapes):
#         self.parameter_name = parameter_name
#         self.parameter_shape = parameter_shape
#         self.valid_shapes = [str(valid_shape) for valid_shape in valid_shapes]
# 
#     def __str__(self):
#         valid_shapes_string = ' or '.join(self.valid_shapes)
#         error_message = "{} wrong shape: Expected: {} Got: {}".format(self.parameter_name, valid_shapes_string,
#                                                                       str(self.parameter_shape))
#         return error_message


# Path: preimage/inference/euler.py
from copy import deepcopy
from preimage.utils.alphabet import get_index_to_n_gram
from preimage.exceptions.n_gram import InvalidNGramLengthError, InvalidYLengthError, NoThresholdsError
from preimage.exceptions.shape import InvalidShapeError
import numpy

    merge the paths together when the graph is not connected. We chose to find the longest path and merge the remaining
    paths after, but this might differ from what Cortes et al. did in their work. The n-gram kernel also has a
    non-unique pre-image. For instance if the string to predict has the n-grams "ab" and "ba" both "aba" and "bab" are
    valid pre-image. In that case we chose randomly a pre-image among the possible ones (this is done by visiting the
    edges and the nodes in a random order). Note that the code could also be faster but we only cared about implementing
    the algorithm correctly.

    References
    ----------
    .. [1] Cortes, Corinna, Mehryar Mohri, and Jason Weston. "A general regression framework for learning
       string-to-string mappings." Predicting Structured Data (2007): 143-168.
    """

    def __init__(self, alphabet, n, min_length=1, is_merging_path=True):
        """Initialize the eulerian path algorithm

        Parameters
        ----------
        alphabet : list
            list of letters.
        n : int
            n-gram length.
        min_length :int
            Minimum length of the predicted string.
        is_merging_path : bool
            True if merges path when the graph is not connected, False otherwise (choose the longest path instead).
        """
        self.n = int(n)
        self.is_merging_path = is_merging_path
        self.min_n_gram_count = 1 if min_length <= self.n else min_length - self.n + 1

        self._index_to_n_gram = get_index_to_n_gram(alphabet, self.n)