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_)