diff --git a/lib/ultra_mastermind_imp.py b/lib/ultra_mastermind_imp.py index 379ff29..b67082c 100644 --- a/lib/ultra_mastermind_imp.py +++ b/lib/ultra_mastermind_imp.py @@ -51,6 +51,43 @@ def new_individual(): # -> set(str) "chromozome": "" } +def randomize(individual, l) -> str: + """ + Methode qui change la valeur d'un chromozome pour une valeur aléatoire + """ + new = "" + for i in range(l): + new += chr(random.randint(0, 255)) + individual["chromozome"] = new + +def fitness1(individual, pm) -> int: + """ + Première methode de fitness, fait la somme des différences entre les codages des caractères des deux chaînes. + """ + sum = 0 + for i in range(len(individual["chromozome"])): + sum += abs(ord(individual["chromozome"][i]) - ord(pm[i])) + return -sum + +def fitness2(individual, pm, alpha) -> int: + """ + Deuxième methode de fitness qui compte les caractères bien placés et mal placés et qui renvoie un int pondéré par alpha + """ + match = 0 + missed_placed = 0 + for i in range(len(individual["chromozome"])): + if individual["chromozome"][i] == pm[i]: + match += 1 + else: + missed_placed += 1 + return match + alpha * missed_placed + +def fitness3(individual, pm) -> int: + """ + Troisième methode de fitness qui utilise la distance de Levenshtein + """ + return -Levenshtein.distance(individual["chromozome"], pm) + def get_fitness(population, individual) -> int: match population["fm"]: case 1: @@ -62,6 +99,29 @@ def get_fitness(population, individual) -> int: case _: return fitness1(individual, population["pm"]) +def get_best(population): + """ + Methode qui renvoie le meilleur individu de la population + """ + fitness_list = [] + for individual in population["individuals"]: + fitness_list.append(get_fitness(population, individual)) + return population["individuals"][max_i(fitness_list)] + +def print_best(population) -> None: + """ + Methode qui affiche le meilleur individu de la population + """ + print(get_best(population)["chromozome"]) + +def mutate(individual) -> None: + """ + Methode qui change un des caractères du chromozome + """ + new = list(individual["chromozome"]) + new[random.randint(0, len(new) - 1)] = chr(random.randint(0, 255)) + individual["chromozome"] = "".join(new) + def select(population) -> None: """ Methode qui sélectionne les meilleurs individus @@ -106,21 +166,6 @@ def mutate_pop(population) -> None: mutate(population["individuals"][to_mutate]) mutated.append(to_mutate) -def get_best(population): - """ - Methode qui renvoie le meilleur individu de la population - """ - fitness_list = [] - for individual in population["individuals"]: - fitness_list.append(get_fitness(population, individual)) - return population["individuals"][max_i(fitness_list)] - -def print_best(population) -> None: - """ - Methode qui affiche le meilleur individu de la population - """ - print(get_best(population)["chromozome"]) - def run(population) -> None: """ Boucle principale @@ -130,48 +175,3 @@ def run(population) -> None: reproduct(population) mutate_pop(population) print_best(population) - -def randomize(individual, l) -> str: - """ - Methode qui change la valeur d'un chromozome pour une valeur aléatoire - """ - new = "" - for i in range(l): - new += chr(random.randint(0, 255)) - individual["chromozome"] = new - -def fitness1(individual, pm) -> int: - """ - Première methode de fitness, fait la somme des différences entre les codages des caractères des deux chaînes. - """ - sum = 0 - for i in range(len(individual["chromozome"])): - sum += abs(ord(individual["chromozome"][i]) - ord(pm[i])) - return -sum - -def fitness2(individual, pm, alpha) -> int: - """ - Deuxième methode de fitness qui compte les caractères bien placés et mal placés et qui renvoie un int pondéré par alpha - """ - match = 0 - missed_placed = 0 - for i in range(len(individual["chromozome"])): - if individual["chromozome"][i] == pm[i]: - match += 1 - else: - missed_placed += 1 - return match + alpha * missed_placed - -def fitness3(individual, pm) -> int: - """ - Troisième methode de fitness qui utilise la distance de Levenshtein - """ - return -Levenshtein.distance(individual["chromozome"], pm) - -def mutate(individual) -> None: - """ - Methode qui change un des caractères du chromozome - """ - new = list(individual["chromozome"]) - new[random.randint(0, len(new) - 1)] = chr(random.randint(0, 255)) - individual["chromozome"] = "".join(new) diff --git a/lib/ultra_mastermind_pp_imp.py b/lib/ultra_mastermind_pp_imp.py new file mode 100644 index 0000000..c03a35e --- /dev/null +++ b/lib/ultra_mastermind_pp_imp.py @@ -0,0 +1,185 @@ +# Librairie du projet en version impératif + +import random +import Levenshtein + +def min_i(array: list[int]) -> int: + min_val = array[0] + min_i = 0 + for i in range(len(array)): + if array[i] < min_val: + min_val = array[i] + min_i = i + return min_i + +def max_i(array: list[int]) -> int: + max_val = array[0] + max_i = 0 + for i in range(len(array)): + if array[i] > max_val: + max_val = array[i] + max_i = i + return max_i + + +def new_population(pm, ng, n, ts, tm, alpha, fm): # -> set(list(set(str)), str, int, int, int, float, float, float, int) + """ + fonction qui renvoie une nouvelle population + """ + population = { + "individuals": [new_individual() for i in range(n)], + "pm": pm, + "ng": ng, + "l": len(pm), + "n": n, + "ts": ts, + "tm": tm, + "alpha": alpha, + "fm": fm + } + + for individual in population["individuals"]: + randomize(individual, 4, 30) + + return population + +def new_individual(): # -> set(str) + """ + fonction qui renvoie un nouvel individu + """ + return { + "chromozome": "" + } + +def randomize(individual, min_l, max_l) -> str: + """ + Methode qui change la valeur d'un chromozome pour une valeur aléatoire + """ + new = "" + for i in range(random.randint(min_l, max_l)): + new += chr(random.randint(0, 255)) + individual["chromozome"] = new + +def fitness1(individual, pm) -> int: + """ + Première methode de fitness, fait la somme des différences entre les codages des caractères des deux chaînes. + """ + sum = 0 + for i in range(len(individual["chromozome"])): + sum += abs(ord(individual["chromozome"][i]) - ord(pm[i])) + return -sum + +def fitness2(individual, pm, alpha) -> int: + """ + Deuxième methode de fitness qui compte les caractères bien placés et mal placés et qui renvoie un int pondéré par alpha + """ + match = 0 + missed_placed = 0 + for i in range(len(individual["chromozome"])): + if i > len(pm): + missed_placed += len(individual["chromozome"]) - len(pm) + break + elif individual["chromozome"][i] == pm[i]: + match += 1 + else: + missed_placed += 1 + if len(pm) > len(individual["chromozome"]): + missed_placed += len(pm) - len(individual["chromozome"]) + return match + alpha * missed_placed + +def fitness3(individual, pm) -> int: + """ + Troisième methode de fitness qui utilise la distance de Levenshtein + """ + return -Levenshtein.distance(individual["chromozome"], pm) + +def get_fitness(population, individual) -> int: + match population["fm"]: + case 1: + return fitness1(individual, population["pm"]) + case 2: + return fitness2(individual, population["pm"], population["alpha"]) + case 3: + return fitness3(individual, population["pm"]) + case _: + return fitness1(individual, population["pm"]) + +def get_best(population): + """ + Methode qui renvoie le meilleur individu de la population + """ + fitness_list = [] + for individual in population["individuals"]: + fitness_list.append(get_fitness(population, individual)) + return population["individuals"][max_i(fitness_list)] + +def print_best(population) -> None: + """ + Methode qui affiche le meilleur individu de la population + """ + print(get_best(population)["chromozome"]) + +def mutate(individual) -> None: + """ + Methode qui change un des caractères du chromozome + """ + new = list(individual["chromozome"]) + if random.randint(1, 2) == 1: new.insert(random.randint(0, len(new) - 1), chr(random.randint(0, 255))) + else : new[random.randint(0, len(new) - 1)] = chr(random.randint(0, 255)) + individual["chromozome"] = "".join(new) + +def select(population) -> None: + """ + Methode qui sélectionne les meilleurs individus + """ + fitness_list = [] + for individual in population["individuals"]: + fitness_list.append(get_fitness(population, individual)) + + for i in range(int((1 - population["ts"]) * population["n"])): + least = min_i(fitness_list) + fitness_list.pop(least) + population["individuals"].pop(least) + +def reproduct(population) -> None: + """ + Methode qui reproduit les individus entre eux jusqu'à obtenir une population de taille N + """ + new = [] + while len(population["individuals"]) + len(new) != population["n"]: + i = random.randint(0, len(population["individuals"]) - 1) + j = random.randint(0, len(population["individuals"]) - 1) + while i == j: + j = random.randint(0, len(population["individuals"]) - 1) + indivi_1 = population["individuals"][i] + indivi_2 = population['individuals'][j] + avg = (len(indivi_1) + len(indivi_2)) // 2 + cut = random.randint(avg // 3, 2 * avg // 3) + while cut > len(indivi_1) or cut > len(indivi_2): cut = random.randint(avg // 3, 2 * avg // 3) + new_chromozome = indivi_1["chromozome"][:cut] + indivi_2["chromozome"][-cut:] + child = new_individual() + child["chromozome"] = new_chromozome + new.append(child) + population["individuals"] += new + +def mutate_pop(population) -> None: + """ + Methode qui mute une partie de la population selon le taut de mutation + """ + mutated = [] + for i in range(int(population["tm"] * population["n"])): + to_mutate = random.randint(0, population["n"] - 1) + while to_mutate in mutated: + to_mutate = random.randint(0, population["n"] - 1) + mutate(population["individuals"][to_mutate]) + mutated.append(to_mutate) + +def run(population) -> None: + """ + Boucle principale + """ + for i in range(population["ng"]): + select(population) + reproduct(population) + mutate_pop(population) + print_best(population) diff --git a/main.py b/main.py index f9348a4..2f76300 100644 --- a/main.py +++ b/main.py @@ -3,25 +3,34 @@ # project libs importations import lib.ultra_mastermind_obj as libobj import lib.ultra_mastermind_imp as libimp +import lib.ultra_mastermind_pp_imp as libppimp # constants -PM = "Hello, world!" -NG = 2000 -N = 400 +PM = "" +NG = 500 +N = 200 TS = 0.5 -TM = 0.01 +TM = 0.25 ALPHA = 0.5 -FITNESS_METHOD = 1 +FITNESS_METHOD = 3 # main function def main() -> None: + # Get phrase from user + PM = input("Entrez une chaîne de caractères : ") + while len(PM) < 4 and len(PM) > 30: PM = input("Entrez une chaîne de caractères : ") + # object version # pop = libobj.Population(pm = PM, ng = NG, n = N, ts = TS, tm = TM, alpha = ALPHA, fm = FITNESS_METHOD) # pop.run() # imperative version - pop = libimp.new_population(PM, NG, N, TS, TM, ALPHA, FITNESS_METHOD) - libimp.run(pop) + # pop = libimp.new_population(PM, NG, N, TS, TM, ALPHA, FITNESS_METHOD) + # libimp.run(pop) + + # imperative version ++ + pop = libppimp.new_population(PM, NG, N, TS, TM, ALPHA, FITNESS_METHOD) + libppimp.run(pop) if __name__ == "__main__": main()