diff --git a/polynomial.py b/polynomial.py index d5dce8c7974afacfe2fde3e0862b52098bc50859..235facc209afac728561d0d7a5aad07960d952f1 100644 --- a/polynomial.py +++ b/polynomial.py @@ -1,29 +1,62 @@ import math -from numbers import Number import itertools -import json -from typing import Tuple -def unicode_superscripts(value): +def unicode_superscripts(number): + """Convert a number into a string of unicode characters superscripts. + + Args: + number (int): The number to convert. + + Returns: + str: The unicode superscripts string. + """ exponent_dict = {"0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", "5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹"} - return ("⁻" if value < 0 else "") + "".join(exponent_dict[x] for x in str(abs(value))) + return ("⁻" if number < 0 else "") + "".join(exponent_dict[x] for x in str(abs(number))) class Polynomial: + """Class for manipulating polynomials.""" + def __init__(self, value=()): + """Creates an instance of Polynomial with a polynomial defined in a tuple. + + Args: + value (tuple, optional): Polynomial defined in a tuple. Defaults to (). + + Raises: + TypeError: The type of the parameter "value" is not a tuple. + """ + if not isinstance(value, tuple): raise TypeError('The "value" parameter is not of type tuple.') self.value = value def pass_x_throughout(self, x): + """Evaluate the polynomial by passing x + + Args: + x (int): The x to evaluate. + + Returns: + int: The result of the evaluation. + """ a = list(self.value) sum = (a[len(a) - 1] * x) + a[len(a) - 2] + for i in reversed(range(len(a) - 2)): sum = sum * x + a[i] return sum def __add__(self, other): + """Add two polynomials. + + Args: + other (Polynomial): The second polynomial to be added. + + Returns: + Polynomial: The result of the addition. + """ a = list(self.value) b = list(other.value) @@ -45,35 +78,51 @@ class Polynomial: return Polynomial(tuple(c)) def __mul__(self, other): + """Multiply two polynomials. + + Args: + other (Polynomial): The second polynomial to be multiplied. + + Returns: + Polynomial: The result of the multiplication. + """ a = list(self.value) b = list(other.value) - a_count = len(a) - b_count = len(b) - size = (a_count - 1) + (b_count - 1) + 1 + size = (len(a) - 1) + (len(b) - 1) + 1 c = [0] * size - for i in range(a_count): - for j in range(b_count): + for i in range(len(a)): + for j in range(len(b)): c[i + j] += a[i] * b[j] - return Polynomial(tuple(c)) def __mod__(self, other): + """Apply a modulo on the polynomial. + + Args: + other (int): The modulo to apply. + + Returns: + Polynomial: The result of the modolu operation. + """ a = list(self.value) result = [0] * len(a) for i in range(len(a)): result[i] = a[i] % other - for i in reversed(range(len(result))): if result[i] == 0: del result[i] else: break - return Polynomial(tuple(result)) def __str__(self): + """Convert a polynomial into a string. + + Returns: + str: The polynomial converts to a string. + """ str_value = "" for i, x in enumerate(reversed(self.value)): @@ -92,67 +141,96 @@ class Polynomial: return str_value -def compute_bachet_bezout(a, b): +def get_bezout_coefficients(a, b): + """Find the Bézout coefficients of a and b. + + Args: + a (int): The number a + b (int): The number b. + + Returns: + tuple: Bézout coefficients. + """ r = [a, b] x = [1, 0] y = [0, 1] q = [0, 0] - - # Computing i = 1 + while r[i] > 0: i += 1 r.append(r[i - 2] % r[i - 1]) q.append(int(r[i - 2] / r[i - 1])) + if r[i] > 0: x.append(x[i - 2] - q[i] * x[i - 1]) y.append(y[i - 2] - q[i] * y[i - 1]) - return x[-1], y[-1] def modular_inverse(a, n): - coefficients = compute_bachet_bezout(a, n) + """Compute the modular inverse of a number a modolu n. + + Args: + a (int): The number to reverse. + n (int): The modolu. + + Returns: + int: The reversed number. + """ + coefficients = get_bezout_coefficients(a, n) + if a * coefficients[0] % n == 1: return coefficients[0] % n return None def compute_lagrange_polynomial(points, prime_number): - nb_points = len(points) - lagrange = Polynomial((0,)) + """Compute the Lagrange polynomial passing through all points. - # Create a polynomial for each points - for i in range(nb_points): - poly_li = Polynomial((1,)) - divider = 1 + Args: + points (list): List of points. + prime_number (int): The prime number. - # Compute the lagrange polynomial - for k in range(nb_points): - if k != i: - dividend = Polynomial((-points[k][0], 1)) # x - value + Returns: + Polynomial: The Lagrange polynomial passing through all points. + """ + # Polynomial L(x) + L_polynomial = Polynomial((0,)) - poly_li *= dividend - divider *= points[i][0] - points[k][0] + # Create the Li(x) polynomial. + for i, (xi, yi) in enumerate(points): + Li_polynomial = Polynomial((1,)) - divider = modular_inverse(divider, prime_number) - point_yi = points[i][1] - poly_li = poly_li * Polynomial((divider,)) * Polynomial((point_yi,)) + # For each point except the point at index i. + for j, (xj, _) in enumerate(points): + if j != i: + Li_polynomial *= Polynomial((-xj, 1)) + Li_polynomial *= Polynomial((modular_inverse(xi - xj, prime_number),)) + Li_polynomial *= Polynomial((yi,)) + L_polynomial += Li_polynomial + return L_polynomial % prime_number - lagrange += poly_li - lagrange %= prime_number - return lagrange +def reed_solomon(points, data_length, last_error_index, prime_number): + """Applies the Reed-Solomon method to correct message errors. + Args: + points (list): List of points. + data_length (int): Data length. + last_error_index (int): The index of the last error. + prime_number (int): The prime number. -def reed_solomon(points, data_length, last_error_index, prime_number): + Returns: + str: The corrected message. + """ combination_length = data_length - len(points[:last_error_index]) # Parse each combination of points possible (exclude the correct points) for x in itertools.combinations(points[: last_error_index + 1], combination_length): nb_valid_points = 0 # Create a sublist of points with all corrects points and the current combination of points - sub_points = list(x) + points[last_error_index + 1:] + sub_points = list(x) + points[last_error_index + 1 :] # Create the lagrange polynomial with the sublist of points lagrange = compute_lagrange_polynomial(sub_points, prime_number) @@ -169,7 +247,7 @@ def reed_solomon(points, data_length, last_error_index, prime_number): # Verify if we have enough valid points, so it must be equal or higher than m + n points if nb_valid_points >= data_length + (len(points) - data_length) // 2: # // = euclid division # Decode the message - output = '' + output = "" for i in range(data_length): output += chr(lagrange.pass_x_throughout(i) % prime_number) @@ -181,7 +259,6 @@ def reed_solomon(points, data_length, last_error_index, prime_number): def main(): message = {"data_length": 25, "last_error_index": 23, "prime_number": 401, "points": [67, 101, 38, 109, 101, 115, 133, 118, 103, 128, 62, 118, 97, 156, 116, 77, 49, 56, 86, 112, 171, 105, 176, 116, 115, 183, 30, 315, 368, 29, 352, 54, 333, 198, 139, 234, 321, 92, 5, 272, 396, 265, 397, 386, 229, 153, 276]} points = [(x, y) for x, y in enumerate(message["points"])] - print(points) corrected_data = reed_solomon(points, message["data_length"], message["last_error_index"], message["prime_number"]) print(corrected_data)