diff --git a/sp.py b/sp.py index a5067d6c67d44678e294718a20f1d60a9cdbc706..da782f6c10ae25b88e472586926cd4355ed99945 100644 --- a/sp.py +++ b/sp.py @@ -1,48 +1,39 @@ import math - import argparse import sys -parser = argparse.ArgumentParser( - description="LEPL1503 - Détection et correction d'erreurs") -parser.add_argument( - "input_file", help="Nom du fichier contenant les données nécessaires") -parser.add_argument("-f", help="Chemin vers le fichier de sortie", - type=argparse.FileType("wb"), default=sys.stdout) -parser.add_argument( - "-v", help="\"verbose\" mode: si ajouté, affiche des informations sur l'exécution du programme", action="store_true") -args = parser.parse_args() - -verbose = args.v -output_fd = args.f -nb_nodes = None -nb_edges = None - -if verbose: - print(args, file=sys.stderr) - - def get_file_infos(data): + """ + Récupère les informations basiques du graphe : le nombre de noeuds et de liens. + """ nb_nodes = int.from_bytes(data[0:4], "big", signed=True) nb_edges = int.from_bytes(data[4:], "big", signed=True) return nb_nodes, nb_edges -def bellman_ford(table, s): - dist = [math.inf]*nb_nodes - dist[s] = 0 - path = [-1]*nb_nodes - for _ in range(nb_nodes-1): - for j in range(len(table)): - a, b, cab = table[j][0], table[j][1], table[j][2] - if (dist[a] != math.inf and dist[b] > dist[a]+cab): - dist[b] = dist[a]+cab - path[b] = a - for j in range(len(table)): - a, b, cab = table[j][0], table[j][1], table[j][2] - if (dist[a] != math.inf and dist[b] > dist[a]+cab): - print("Cycle négatif détecté") +def bellman_ford(links, s, verbose): + """ + Exécute l'algorithme de Bellman-Ford avec comme noeud source `s`. + Retourne un tableau de distances pour atteindre chaque noeud depuis `s`, + ainsi que le chemin pour atteindre ces noeuds (tableau de précédence). + Si le noeud est isolé ou qu'un cycle négatif est trouvé, retourne une distance + infinie pour chaque autre noeud, et une distance de 0 pour `s`. + """ + dist = [math.inf] * nb_nodes + dist[s] = 0 # Le noeud source est à distance 0 de lui-meme. + path = [-1] * nb_nodes + for _ in range(nb_nodes-1): # Iteration de Bellman-Ford. + for j in range(len(links)): + node_from, node_to, cost = links[j][0], links[j][1], links[j][2] + if (dist[node_from] != math.inf and dist[node_to] > dist[node_from] + cost): + dist[node_to] = dist[node_from] + cost + path[node_to] = node_from + for j in range(len(links)): + node_from, node_to, cost = links[j][0], links[j][1], links[j][2] + if (dist[node_from] != math.inf and dist[node_to] > dist[node_from] + cost): + if verbose: + print("Cycle négatif détecté") dist = [math.inf] * nb_nodes dist[s] = 0 path = [-1] * nb_nodes @@ -50,75 +41,104 @@ def bellman_ford(table, s): def get_path(dest, path, source): + """ + Retourne une liste contenant le chemin de `source` vers `dest` + en utilisant le tableau de précédence `path`. + """ r = [dest] i = dest - while (True): - if (i == source): - break + while i != source: r.insert(0, path[i]) i = path[i] return r def get_max(dist, s): - max = -math.inf - n = s - for i in range(len(dist)): - if (i != s and dist[i] != math.inf and dist[i] >= max): - max = dist[i] - n = i - if (max == -math.inf): - if (dist[s] != math.inf and dist[s] >= max): - max = dist[s] + """ + Retourne l'indice du noeud dont il existe un chemin de `s` vers ce noeud + et le cout de ce chemin est le plus élevé parmis tous les noeuds ayant un chemin + depuis `s`. + """ + max_cost = -math.inf + max_node = s + for node_idx in range(len(dist)): + if node_idx != s and dist[node_idx] != math.inf and dist[node_idx] >= max_cost: + max_cost = dist[node_idx] + max_node = node_idx + if max_cost == -math.inf: + if dist[s] != math.inf and dist[s] >= max_cost: + max_cost = dist[s] + + return max_cost, max_node + + +def read_graph(filename): + """ + Récupère le graphe représenté dans le fichier donné en argument. + Suit le format défini dans l'énoncé. + """ + with open(filename, "rb") as fd: + binary_data = fd.read() + nb_nodes, nb_edges = get_file_infos(binary_data[:8]) - return max, n + if verbose: + print("Nombre de noeuds :", nb_nodes, + ", nombre de liens :", nb_edges) + + binary_data = binary_data[8:] + links = [] + + for i in range(nb_edges): + from_node = int.from_bytes(binary_data[i * 12:i * 12 + 4], "big", signed=True) + to_node = int.from_bytes(binary_data[i * 12 + 4:i * 12 + 8], "big", signed=True) + cost = int.from_bytes( + binary_data[i * 12 + 8:i * 12 + 12], "big", signed=True) + l1 = [from_node, to_node, cost] + links.append(l1) + + return links if __name__ == "__main__": - with open(args.input_file, "rb") as input_file: - binary_data = input_file.read() - nb_nodes, nb_edges = get_file_infos(binary_data[:8]) - if verbose: - print("Number of nodes :", nb_nodes, - ", number of links :", nb_edges) + parser = argparse.ArgumentParser( + description="LEPL1503 - Algorithme de plus court chemin") + parser.add_argument( + "input_file", help="chemin vers le fichier d'instance representant le graphe a traiter.") + parser.add_argument("-f", help="chemin vers le fichier qui contiendra le resultat de programme, au format specifie dans l'enonce. Defaut : stdout.", + type=argparse.FileType("wb"), default=sys.stdout) + parser.add_argument( + "-v", help="autorise les messages de debug. Si ce n'est pas active, aucun message de ce type ne peut etre affiche, excepte les messages d'erreur en cas d'echec. Defaut : false.", action="store_true") + args = parser.parse_args() + + verbose = args.v + output_fd = args.f + nb_nodes = None + nb_edges = None + + if verbose: + # Exemple de message que vous pouvez ecrire si le mode verbose est actif. + print(args, file=sys.stderr) + + graph = read_graph(args.input_file) + for source in range(nb_nodes): + dist, path = bellman_ford(graph, source) - binary_data = binary_data[8:] - table = [] if output_fd == sys.stdout or output_fd == sys.stderr: - print(nb_nodes) - + print("source : " + str(source)) + print("Distances: ", dist) + d, n = get_max(dist, source) + print("\tdestination : " + str(n)) + print("\tcout : " + str(d)) + p = get_path(n, path, source) + print("\tnombre de noeuds : "+str(len(p))) + print("\tchemin : "+" ".join(str(x) for x in p)) else: - output_fd.write(nb_nodes.to_bytes(4, "big")) - - for i in range(nb_edges): - a = int.from_bytes(binary_data[i*12:i*12+4], "big", signed=True) - b = int.from_bytes(binary_data[i*12+4:i*12+8], "big", signed=True) - cab = int.from_bytes( - binary_data[i*12+8:i*12+12], "big", signed=True) - l1 = [a, b, cab] - table.append(l1) - for i in range(nb_nodes): - dist, path = bellman_ford(table, i) - if dist == -1: - continue # TODO: if there is no shortest path (negative cycle or empty path), put a 0 value instead of not showing it at all - - if output_fd == sys.stdout or output_fd == sys.stderr: - print("source : "+str(i)) - d, n = get_max(dist, i) - print("destination : " + str(n)) - print("cout : " + str(d)) - p = get_path(n, path, i) - print("nombre de noeuds : "+str(len(p))) - print("chemin : "+" ".join(str(x) for x in p)) - print("-----------------------") - - else: - output_fd.write(i.to_bytes(4, "big")) - d, n = get_max(dist, i) - output_fd.write(d.to_bytes(4, "big", signed=True)) - output_fd.write(n.to_bytes(4, "big")) - r = get_path(n, path, i) - output_fd.write(len(r).to_bytes(4, "big", signed=True)) - for j in range(len(r)): - output_fd.write(r[j].to_bytes(4, "big")) + output_fd.write(source.to_bytes(4, "big")) + d, n = get_max(dist, source) + output_fd.write(d.to_bytes(4, "big", signed=True)) + output_fd.write(n.to_bytes(4, "big")) + r = get_path(n, path, source) + output_fd.write(len(r).to_bytes(4, "big", signed=True)) + for j in range(len(r)): + output_fd.write(r[j].to_bytes(4, "big"))