I'm an autodidact working on map coloring algorithms with backtracking, see chapter 2 of this book.
I tested this implementation with success but I'm still unsure whether it is tricking me somewhere, or if I could improve it.
Color = int def paint(graph: dict[Node, list[Node]]) -> list[tuple[Node, Color]]: """An interface.""" def init(nodes: list[Node]) -> list[tuple[Node, Color]]: """Starts out with no colors assigned""" return [(n, -1) for n in nodes] def is_legal(color: Color, node: Node, ns: list[tuple[Node, Color]]) -> bool: """True if parent's color is different from childrens' color""" for child in graph[node]: if (child, color) in ns: return False return True def _paint(i: int, ns: list[tuple[Node, Color]], max_colors: int): """Helper function""" if i == len(ns): return ns # we did it if i < 0: # tried everything possible but nope return _paint(0, init(sorted_adjacent), max_colors + 1) node, color = ns[i] if color + 1 == max_colors: return _paint(i - 1, ns, max_colors) # backtrack ns[i] = (node, color + 1) if is_legal(color + 1, node, ns): return _paint(i + 1, ns, max_colors) # go to next node else: return _paint(i, ns, max_colors) # try with next color sorted_adjacent = sorted(graph.keys(), key=lambda r: -len(graph[r])) return _paint(0, init(sorted_adjacent), 2)
This is the rest of the code, with an example graph input, which is defined as a simple dictionary with nodes as keys, and a list of nodes (the adjacent regions) as values:
class Node: def __init__(self, s: str): self.s = s def __repr__(self): return f"Node('{self.s}')" if __name__ == "__main__": graph_ = { (n1 := Node("1")): [(n2 := Node("2"))], n2: [n1, (n3 := Node("3")), (n4 := Node("4")), (n5 := Node("5")), (n6 := Node("6"))], n3: [n2, n4], n4: [n2, n3, n5], n5: [n2, n4, n6], n6: [n2, n5], } output = paint(graph_)