From d58421f97c5d4769476e60e57b8228a4156eb701 Mon Sep 17 00:00:00 2001 From: Andreas Fruhwirt Date: Mon, 15 Jan 2024 13:20:49 +0100 Subject: [PATCH] Finalized algorithm --- Readme.md | 14 +++- main.py | 215 +++++++++++++++++++++++------------------------------- 2 files changed, 105 insertions(+), 124 deletions(-) diff --git a/Readme.md b/Readme.md index 332e850..285cd76 100644 --- a/Readme.md +++ b/Readme.md @@ -14,4 +14,16 @@ uncolored, and proceeds with the next iteration.* The code may be written in any programming language. Submit your code via GIT (link the repository in your submission) including instructions on how to run it. Additionally provide several example inputs (on at least a few hundred nodes) and a -test case that your algorithm computes a proper vertex coloring. \ No newline at end of file +test case that your algorithm computes a proper vertex coloring. + +Run the program with the following statement: +``` +$ python main.py <#nodes> +``` +or run it with a graph that has 100 to 300 nodes: +``` +$ python main.py +``` +All the edges are generated with a random percentage chance between 0% and 100%. + +Every execution the program checks if the algorithm ran correctly. \ No newline at end of file diff --git a/main.py b/main.py index d674a4e..a44ee56 100644 --- a/main.py +++ b/main.py @@ -3,148 +3,117 @@ from random import randint, random import networkx as nx import sys -class Node(object): +DEBUG = False - def __init__(self, id, g): - self.graph = g - self.id = id - self.color = None - self.candidate_list = None - self.queue = "start" - self.next_tick = None - self.edges = [] - self.done = False +class NodeInformation: + def __init__(self, color_amt): + self.available_colors = [i for i in range(0, color_amt)] + self.neighbor_colors = [] + self.is_done = False + self.assigned_color = None - def add_edge(self, node): - self.edges.append(node) +color_amt = None +ni = {} +g = None - def print(self): - print(f"Node {self.id} = (c:{self.color}, #e:{len(self.edges)})") +def run_tick(): + global color_amt, ni, g + if DEBUG: print("===== RUN TICK =====") + # run for each node + for n in g.nodes: + if ni[n].is_done: + continue + neighbors = list(g.adj[n]) - def run_tick(self): - if not self.done: - # candidate list of colors - self.candidate_list = self.graph.possible_colors.copy() - if not self.next_tick == "start": - # if its not the first iteration w/o data, filter out all neighbor colors from candidate list - neighbor_candidates = self.next_tick.copy() - for colore in neighbor_candidates: - if colore in self.candidate_list: - self.candidate_list.remove(colore) + # each uncolored node selects random candidate color + available_colors = ni[n].available_colors + ni[n].assigned_color = ni[n].available_colors[randint(0, len(available_colors)-1)] + if DEBUG: print(f"{n} | assigned_color: {ni[n].assigned_color}") + if DEBUG: print(f"{n} | neighbors: {neighbors}") - # if color is in candidate list (no neighbors has this color), finish. - # do not run this at start - if self.color in self.candidate_list and self.color is not None: - self.done = True - self.queue = "stop" - #print(f"{self.id} | is done") - return - - # pick random color from candidate list - self.color = self.candidate_list[randint(0, len(self.candidate_list) - 1)] + # nodes exchange candidate colors + for neigh in neighbors: + if not ni[neigh].is_done: + ni[neigh].neighbor_colors.append(ni[n].assigned_color) - #print(f"{self.id} | possible colors: {self.candidate_list}, current color: {self.color}") + # run again, but only after neighbor_colors assigned for all nodes + for n in g.nodes: + if ni[n].is_done: + continue + neighbors = list(g.adj[n]) - # send data to the other neighbors - for n in self.edges: - if n.queue != "stop": - n.queue.append(self.color) - #print(f"{self.id} => sending {self.color} to N[{n.id}]") + if DEBUG: print(f"{n} | neighbor_colors: {ni[n].neighbor_colors}, assigned_color: {ni[n].assigned_color}") + # check if assigned color is used by neighbor + if ni[n].assigned_color in ni[n].neighbor_colors: + # reset + ni[n].assigned_color = None + ni[n].neighbor_colors = [] + else: + # get permantly colored + ni[n].is_done = True + # remove color from available colors of neighbor + for neigh in neighbors: + if not ni[neigh].is_done: + if ni[n].assigned_color in ni[neigh].available_colors: + ni[neigh].available_colors.remove(ni[n].assigned_color) -class Graph(object): + is_done = True + for n in g.nodes: + is_done &= ni[n].is_done + + return 0 if is_done else 1 - def __init__(self): - self.V = None - self.max_degree = None - - def load_graph_file(self, graph_file): - with open(graph_file, "r") as f: - V_amt, E_amt = map(int, f.readline().split(" ")) - self.V = [Node(x, self) for x in range(1, V_amt+1)] - self.E = {} - self.max_degree = 0 - for i in range(0, E_amt): - a, b = map(int, f.readline().split(" ")) - a -= 1 - b -= 1 - self.V[a].add_edge(self.V[b]) - self.V[b].add_edge(self.V[a]) - self.max_degree = max(self.max_degree, len(self.V[a].edges), len(self.V[b].edges)) - - self.possible_colors = [x for x in range(1, self.max_degree + 2)] - - def load_graph_networkx(self, graph): - self.V = [Node(x, self) for x in range(1, graph.number_of_nodes() + 1)] - self.E = {} - self.max_degree = 0 - for a, b in graph.edges: - self.V[a].add_edge(self.V[b]) - self.V[b].add_edge(self.V[a]) - self.max_degree = max(self.max_degree, len(self.V[a].edges), len(self.V[b].edges)) - - self.possible_colors = [x for x in range(1, self.max_degree + 2)] - - def run_tick(self): - print("Next Tick") - # send data - for n in self.V: - n.next_tick = n.queue - n.queue = [] - # run tick for each - for n in self.V: - n.run_tick() - - done = True - for n in self.V: - done &= n.done - - if done: - print("Stop") - return -1 - pass - - def print_graph(self): - print(f"Max Degree: {self.max_degree}") - for n in self.V: - n.print() - - def check_correctness(self): - for n in self.V: - color = n.color - for neighbor in n.edges: - if neighbor.color == color: - print("=====================") - n.print() - neighbor.print() - print(f"Neighbor {neighbor.id} has same color as {n.id}") - print("=====================") - return False - return True +def check_correctness(): + global color_amt, ni, g + total_colors = [] + for n in g.nodes: + if ni[n].assigned_color not in total_colors: + total_colors.append(ni[n].assigned_color) + neighbors = list(g.adj[n]) + for neigh in neighbors: + if ni[n].assigned_color == ni[neigh].assigned_color: + print(f"FAIL: {n} and {neigh} have same color {ni[n].assigned_color}") + assert False + + if len(total_colors) > color_amt: + print(f"FAIL: color_amt not correct: {len(total_colors)} <= {color_amt}") + assert False def main(): - g = Graph() + global color_amt, ni, g + amt_nodes = randint(100, 300) if len(sys.argv) == 2: - filename = sys.argv[1] - g.load_graph(filename) - else: - G = nx.erdos_renyi_graph(randint(100, 300), random()) - g.load_graph_networkx(G) - - g.print_graph() + amt_nodes = int(sys.argv[1]) - for i in range(0, 10): - ret = g.run_tick() - if ret == -1: + g = nx.erdos_renyi_graph(amt_nodes, random()) + + # find out max degree + max_degree = 0 + for n in g.nodes: + max_degree = max(max_degree, g.degree[n]) + + color_amt = max_degree + 1 + print(f"Amount of Colors: {color_amt}") + + for n in g.nodes: + ni[n] = NodeInformation(color_amt) + + ticks = 0 + while True: + ret = run_tick() + ticks += 1 + if ret == 0: # print info print("Final colors:") - for n in g.V: - n.print() + for n in g.nodes: + print(f"{n}[{ni[n].assigned_color}] ", end="") + print() + print(f"Amount of Ticks: {ticks}") # assert if correct - assert g.check_correctness() + check_correctness() exit(0) - pass if __name__ == '__main__': main() \ No newline at end of file