def lattice(iter_or_int: IterOrInt = None, *, width: Optional[int] = None, height: Optional[int] = None) -> Graph: if iter_or_int is None: if width is None or height is None: raise ValueError( 'if iter_or_int is None, width and height must be provided.' ) iter_or_int = width * height vertices = list(_int_to_range(iter_or_int)) if len(vertices) == 0: raise ValueError( 'iterable is empty' ) width, height = _discover_width_and_height( len(vertices), width, height ) matrix = [vertices[i:i + width] for i in range(0, len(vertices), width)] transposed = [list(z) for z in zip(*matrix)] edges = _parallel_edges(matrix) | _parallel_edges(transposed) return Graph( vertices, edges )
def _dijkstra( g: Graph, start: Vertex, ) -> Tuple[Dict[Vertex, float], Dict[Vertex, Optional[Vertex]]]: distance = {v: inf for v in g.vertices} distance[start] = 0 previous: Dict[Vertex, Optional[Vertex]] previous = {v: None for v in g.vertices} unvisited: Set[Vertex] = g.vertices while len(unvisited) > 0: (current, _) = min({(v, distance[v]) for v in unvisited}, key=lambda t: t[1]) unvisited.remove(current) for n in g.neighbors(current): distn = distance[current] + g.weight[current, n] if distn < distance[n]: distance[n] = distn previous[n] = current return distance, previous
def biclique(iter_or_int1: IterOrInt, iter_or_int2: IterOrInt) -> Graph: vertices1 = set(_int_to_range(iter_or_int1)) vertices2 = set(_int_to_range(iter_or_int2)) return Graph( vertices1 | vertices2, product(vertices1, vertices2) )
def prim(g: Graph) -> Graph: """ Constructs a minimum spanning tree using Prim's algorithm """ cost: Dict[Vertex, float] = {v: inf for v in g.vertices} edge: Dict[Vertex, Vertex] = {v: None for v in g.vertices} tree = Graph() vertices_left = g.vertices while vertices_left: # find the vertex in the fringe with the minimal cost (current_vertex, current_cost) = min( cost.items(), key=lambda t: t[1], ) for v in g.neighbors(current_vertex): if v in cost and g.weight[v, current_vertex] < cost[v]: cost[v] = g.weight[v, current_vertex] edge[v] = current_vertex tree.insert(current_vertex) if current_cost != inf: tree.link(current_vertex, edge[current_vertex], cast(int, current_cost)) del edge[current_vertex] del cost[current_vertex] vertices_left.remove(current_vertex) return tree
def complete(iter_or_int: IterOrInt) -> Graph: vertices = set(_int_to_range(iter_or_int)) return Graph( vertices, { (min(v1, v2), max(v1, v2)) for v1, v2 in product(vertices, vertices) if v1 is not v2 } )
def binary_tree(iter_or_int: IterOrInt) -> Graph: vertices = list(_int_to_range(iter_or_int)) if len(vertices) == 0: raise ValueError( 'tree cannot be empty' ) g = Graph(vertices) for i, _ in enumerate(vertices): for offset in {1, 2}: j = i * 2 + offset if j < len(vertices): g.link( vertices[i], vertices[j], ) return g
def coloring(g: Graph) -> Dict[Vertex, int]: colors: Dict[Vertex, int] = {} for v in g.vertices: available = [True] * g.order for adj in g.neighbors(v): if colors.get(adj) is not None: available[colors[adj]] = False colors[v] = available.index(True) return colors
def fringe(g: Graph, selected: Iterable[Vertex]) -> Set[Vertex]: selected = set(selected) if not selected.issubset(g.vertices): raise ValueError("selected is not a subset of the graph's vertices") fr = set() for v in selected: for v2 in g.neighbors(v): if v2 not in selected: fr.add(v2) return fr
def transitive_closure(g: Graph, v: Vertex, visited: Optional[Set[Vertex]] = None) -> Set[Vertex]: """ Returns a set containing all vertices reachable from v """ visited = visited or set() visited.add(v) for v_neigh in g.neighbors(v): if v_neigh not in visited: transitive_closure(g, v_neigh, visited) return visited
def kruskal(g: Graph) -> Graph: """ Constructs a minimum spanning tree using Kruskal's algorithm The input graph must not have parallel edges or loops """ for v1, v2, _ in g.edges: if v1 == v2: raise ValueError("g can't have loops") edges = sorted(g.edges, key=lambda e: e[2], reverse=True) t = Graph(g.vertices) n_edges = 0 while t.order - 1 > n_edges: v1, v2, w = edges.pop() if v1 not in transitive_closure(t, v2): t.link(v1, v2, w) n_edges += 1 return t
def bfs(g: Graph, start: Vertex, condition: Test) -> Optional[Vertex]: deq: Deque[Vertex] = deque() visited = set() deq.append(start) visited.add(start) while len(deq) > 0: cur = deq.popleft() if condition(cur): return cur for n in g.neighbors(cur): if n not in visited: deq.append(n) visited.add(n) return None
def to_dot( g: Graph, to_str: VertexToString = str, force_weight: bool = False, edge_color: EdgeToColor = None ) -> str: edge_color = edge_color or (lambda *args: None) dot = ['graph {\n'] for v1, v2, w in g.edges: dot += ['"', to_str(v1), '" -- "', to_str(v2), '"'] opts = [] if w != 1 or force_weight: opts.append(f'label="{str(w)}"') color = edge_color(v1, v2, w) if color is not None: opts.append(f'color="{color}"') if len(opts) > 0: dot += [ ' [', *opts, ']' ] dot.append(';\n') for v in g.vertices: if len(g.neighbors(v)) == 0: dot += ['"', to_str(v), '";\n'] dot.append('}') return ''.join(dot)
def dfs(g: Graph, current: Vertex, condition: Test, visited: Set = None) -> Optional[Vertex]: visited = visited or set() if current in visited: return None visited.add(current) if condition(current): return current for n in g.neighbors(current): v = dfs(g, n, condition, visited) if v is not None: return v return None
def _cycle_with(g: Graph, v: Vertex, v_prev: Vertex, visited: Set[Vertex] = None) -> bool: """ Returns True if there is a cycle in the graph containing v, False otherwise """ visited = visited or set() if v in visited: return True visited.add(v) for v_neigh in g.neighbors(v): if v_neigh != v_prev: if _cycle_with(g, v_neigh, v, visited): return True visited.remove(v) return False
def hamiltonian_cycle(g: Graph, start: Vertex) -> List[Vertex]: path = [start] current = start visited: Set[Vertex] = set() try: while len(visited) != g.order: visited.add(current) (_, nearest) = min((g.weight[current, v], v) for v in g.neighbors(current) if v not in visited) path.append(nearest) current = nearest except ValueError as e: if len(path) == g.order: return path raise HamiltonianCycleNotFound('graph has dead ends')
def floyd_warshall(g: Graph) -> Dict[Vertex, Dict[Vertex, float]]: dist: Dict[Vertex, Dict[Vertex, float]] = {} vertices = g.vertices for v1 in vertices: dist[v1] = {} for v2 in vertices: if v1 is v2: dist[v1][v2] = 0 elif g.has_edge(v1, v2): dist[v1][v2] = g.weight[v1, v2] else: dist[v1][v2] = inf for k in vertices: for i in vertices: for j in vertices: if dist[i][j] > dist[i][k] + dist[k][j]: dist[i][j] = dist[i][k] + dist[k][j] return dist