dsa.graph
Module containing graph classes.
1""" Module containing graph classes. """ 2 3from dsa.queue import Queue 4 5class AdjacencyMatrixGraph: 6 """ 7 An unweighted adjacency matrix graph implementation. 8 9 This class allows either directed or undirected representation of a graph. 10 Vertex labels are string types. 11 """ 12 def __init__(self, labels: list[str]): 13 """ 14 Initialize the graph with a list of vertex labels. 15 16 Args: 17 labels (list[str]): List of labels for each vertex. 18 """ 19 self.labels = labels 20 self.label_index = { label: index for index, label in enumerate(labels) } 21 22 node_count = len(self.labels) 23 self.array = [[None for i in range(node_count)] for j in range(node_count)] 24 25 def add_edge(self, start_label: str, end_label: str, directed=False): 26 """ 27 Add an edge in the graph. 28 29 Args: 30 start_label (str): Starting vertex label. 31 end_label (str): Ending vertex label. 32 directed (bool): Whether the edge is directed. 33 """ 34 a = self.label_index[start_label] 35 b = self.label_index[end_label] 36 self.array[a][b] = True 37 if not directed: 38 self.array[b][a] = True 39 40 def add_vertex(self, label: str): 41 """ 42 Add a vertex to the graph. 43 44 Args: 45 label (str): The vertex label to add. 46 """ 47 self.labels.append(label) 48 self.label_index[label] = len(self.labels) - 1 49 for row in self.array: 50 row.append(None) 51 self.array.append([None for i in range(len(self.labels))]) 52 53 def delete_vertex(self, label: str): 54 """ 55 Delete a vertex from the graph. 56 57 Args: 58 label (str): The vertex label to delete. 59 """ 60 index = self.label_index[label] 61 self.labels.pop(index) 62 self.label_index = { label: index for index, label in enumerate(self.labels) } 63 self.array.pop(index) 64 for row in self.array: 65 row.pop(index) 66 67 def delete_edge(self, start_label: str, end_label: str, directed=False): 68 """ 69 Delete an edge in the graph. 70 71 Args: 72 start_label (str): Starting vertex label. 73 end_label (str): Ending vertex label. 74 directed (bool): Whether the edge is directed. 75 """ 76 a = self.label_index[start_label] 77 b = self.label_index[end_label] 78 if self.array[a][b] is None: 79 raise KeyError(f"Edge {start_label} to {end_label} does not exist") 80 81 self.array[a][b] = None 82 if not directed: 83 if self.array[b][a] is None: 84 raise KeyError(f"Edge {end_label} to {start_label} does not exist") 85 self.array[b][a] = None 86 87 def dfs_traverse(self, start_label: str): 88 """ 89 Perform depth first traversal in an adjacency matrix 90 91 Args: 92 start_label (str): Starting vertex label. 93 94 Returns: 95 Array with depth first order traversal. 96 """ 97 return self._df_rec_traverse(start_label, set(), []) 98 99 def _df_rec_traverse(self, start_label: str, visited, dfs): 100 """ 101 Helper method for depth first recursive traversal 102 """ 103 start_index = self.label_index[start_label] 104 visited.add(start_label) 105 dfs.append(self.labels[start_index]) 106 107 for adj_index, is_connected in enumerate(self.array[start_index]): 108 adj_label = self.labels[adj_index] 109 if is_connected and adj_label not in visited: 110 self._df_rec_traverse(adj_label, visited, dfs) 111 112 return dfs 113 114 def bfs_traverse(self, start_label: str): 115 """ 116 Perform breadth first traversal in an adjacency matrix. 117 118 Args: 119 start_label (str): Starting vertex label. 120 121 Returns: 122 Array with breadth first order traversal. 123 """ 124 bfs = [] 125 q = Queue() 126 visited = set() 127 128 start_index = self.label_index[start_label] 129 q.enqueue(start_index) 130 bfs.append(self.labels[start_index]) 131 while not q.is_empty(): 132 current = q.dequeue() 133 for i in range(len(self.array)): 134 if start_index != i and self.array[current][i] and (i not in visited): 135 visited.add(i) 136 q.enqueue(i) 137 bfs.append(self.labels[i]) 138 return bfs 139 140 def vertices(self) -> list: 141 """ 142 Return a list of vertex labels of the graph 143 """ 144 return self.labels 145 146 def edges(self) -> list: 147 """ 148 Return a list of edges in the graph. Each edge is represented by a tuple (start, end) 149 """ 150 edges = [] 151 vertex_count = len(self.labels) 152 for i in range(vertex_count): 153 for j in range(vertex_count): 154 if self.array[i][j] and i != j: 155 edges.append((self.labels[i], self.labels[j])) 156 157 return edges 158 159 def undirected_edges(self) -> list: 160 """ 161 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end) 162 """ 163 edges = [] 164 vertex_count = len(self.labels) 165 for i in range(vertex_count): 166 for j in range(vertex_count): 167 if self.array[i][j] and i != j and (self.labels[j], self.labels[i]) not in edges: 168 edges.append((self.labels[i], self.labels[j])) 169 170 return edges 171 172 def is_edge(self, start_label: str, end_label: str) -> bool: 173 """ 174 Return boolean if an edge exists. 175 176 Args: 177 start_label (str): starting vertex label 178 end_label (str): starting vertex label 179 180 Returns:0 181 A boolean of whether there is an edge from start to end. 182 """ 183 start_index = self.label_index[start_label] 184 end_index = self.label_index[end_label] 185 186 return self.array[start_index][end_index] is not None 187 188 def __getitem__(self, vertex: str) -> list: 189 """ 190 Args: 191 vertex (str): The vertex label. 192 Returns: 193 A list of adjacent vertex labels. 194 """ 195 index = self.label_index[vertex] 196 return [self.labels[i] for i in range(len(self.array[index])) if self.array[index][i]] 197 198 def print_graph(self): 199 """ 200 Print the contents of the graph. 201 """ 202 print(" |", end="") 203 for label in self.labels: 204 print(f"{label:^3}", end=" ") 205 print() 206 print("----" * (len(self.array) + 1)) 207 for r, row in enumerate(self.array): 208 label = self.labels[r] 209 print(f"{label:^3}|", end="") 210 for col in row: 211 b = " T " if col else " " 212 print(b, end=" ") 213 print() 214 215class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph): 216 """ 217 A weighted adjacency matrix graph implementation 218 (allows either directed or undirected representation) 219 vertex labels are string types 220 """ 221 def __init__(self, labels): 222 """ 223 Args: 224 labels: list of labels for each vertex (string types) 225 """ 226 super().__init__(labels) 227 228 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 229 """ 230 Add an edge to the graph. 231 232 Args: 233 start_label (str): The starting vertex label. 234 end_label (str): The ending vertex label. 235 weight: The weight of the vertex. 236 directed: Whether the edge is directed. 237 """ 238 a = self.label_index[start_label] 239 b = self.label_index[end_label] 240 241 self.array[a][b] = weight 242 self.array[a][a] = 0 243 self.array[b][b] = 0 244 245 if not directed: 246 self.array[b][a] = weight 247 248 def print_graph(self): 249 """ 250 Print the contents of the graph. 251 """ 252 print(" |", end="") 253 for label in self.labels: 254 print(f"{label:>3}", end=" ") 255 print() 256 print("----" * (len(self.array) + 1)) 257 for r, row in enumerate(self.array): 258 label = self.labels[r] 259 print(f"{label:^3}|", end="") 260 for col in row: 261 w = f"{col:3}" if col is not None else " " 262 print(w, end=" ") 263 print() 264 265 def edges(self) -> list: 266 """ 267 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight). 268 """ 269 edges = [] 270 vertex_count = len(self.labels) 271 for i in range(vertex_count): 272 for j in range(vertex_count): 273 weight = self.array[i][j] 274 if weight and i != j: 275 edges.append((self.labels[i], self.labels[j], weight)) 276 277 return edges 278 279 def undirected_edges(self) -> list: 280 """ 281 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight). 282 """ 283 edges = [] 284 vertex_count = len(self.labels) 285 for i in range(vertex_count): 286 for j in range(vertex_count): 287 weight = self.array[i][j] 288 if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges: 289 edges.append((self.labels[i], self.labels[j], weight)) 290 291 return edges 292 293 def is_edge(self, start_label: str, end_label: str) -> bool: 294 """ 295 Return boolean if an edge exists. 296 297 298 Args: 299 start_label (str): Starting vertex label. 300 end_label (str): Ending vertex label. 301 302 Returns: 303 A boolean of whether there is an edge from start to end. 304 """ 305 return super().is_edge(start_label, end_label) 306 307 def weightx(self, start: str, end: str) -> bool: 308 """ 309 Return weight of an edge (is this used???) 310 Args: 311 start_label: starting vertex label 312 end_label: starting vertex label 313 314 Returns: 315 weight value of an edge from start to end 316 """ 317 return super().is_edge(start, end) 318 319 def __getitem__(self, vertex: str) -> dict: 320 """ 321 Args: 322 vertex: vertex label 323 Returns: 324 a dictionary of adjacent vertex labels and weights 325 """ 326 index = self.label_index[vertex] 327 return {self.labels[i] : self.array[index][i] for i in range(len(self.array[index])) if self.array[index][i]} 328 329class AdjacencyListGraph: 330 """ 331 A unweighted adjacency list vertex implementation 332 (allows either directed or undirected representation) 333 vertex labels are string types 334 """ 335 def __init__(self): 336 #: hash table of vertices in graph 337 self._adjacents = {} 338 339 def add_edge(self, start_label: str, end_label: str, directed=False): 340 """ 341 Add an edge to the graph. 342 343 Args: 344 start_label (str): The label of the starting vertex. 345 end_label (str): The label of the ending vertex. 346 directed (bool): Whether the edge is directed. 347 """ 348 self.add_directed_edge(start_label, end_label) 349 if not directed: 350 self.add_directed_edge(end_label, start_label) 351 352 def add_directed_edge(self, start_label: str, end_label: str): 353 """ 354 Add a directed edge to the graph. 355 356 Args: 357 start_label (str): The label of the starting vertex. 358 end_label (str): The label of the ending vertex. 359 """ 360 if start_label not in self._adjacents: 361 self._adjacents[start_label] = [end_label] 362 else: 363 if end_label not in self._adjacents[start_label]: 364 self._adjacents[start_label].append(end_label) 365 if end_label not in self._adjacents: 366 self._adjacents[end_label] = [] 367 368 def delete_edge(self, start_label: str, end_label: str, directed=False): 369 """ 370 Delete an edge in the graph. 371 372 Args: 373 start_label (str): Starting vertex label. 374 end_label (str): Ending vertex label. 375 directed (bool): Whether the edge is directed. 376 Raises: 377 KeyError: If the edge does exist. 378 """ 379 if start_label not in self._adjacents: 380 raise KeyError(f"Vertex {start_label} does not exist") 381 if end_label not in self._adjacents[start_label]: 382 raise KeyError(f"Vertex {end_label} does not exist") 383 self._adjacents[start_label].remove(end_label) 384 if not directed: 385 if end_label not in self._adjacents: 386 raise KeyError(f"Vertex {end_label} does not exist") 387 if start_label not in self._adjacents[end_label]: 388 raise KeyError(f"Vertex {start_label} does not exist") 389 self._adjacents[end_label].remove(start_label) 390 391 def vertices(self) -> list: 392 """ 393 Return a list of vertex labels of the graph 394 """ 395 return list(self._adjacents.keys()) 396 397 def adjacents(self, vertex: str) -> list: 398 """ 399 Return a list of adjacents of a given vertex 400 401 Args: 402 vertex (str): The label of the vertex. 403 """ 404 return self._adjacents[vertex] 405 406 def dfs_traverse(self, start_label: str): 407 """ 408 Return a list of vertices through depth first traversal starting at a given vertex. 409 410 Args: 411 start_label (str): The starting vertex label. 412 Returns: 413 A list of vertex labels. 414 """ 415 return self._df_rec_traverse(start_label, set(), []) 416 417 def _df_rec_traverse(self, start_label: str, visited, dflist): 418 """ 419 Helper depth first traversal recursive function. 420 """ 421 visited.add(start_label) 422 dflist.append(start_label) 423 424 for v in self[start_label]: 425 if v not in visited: 426 self._df_rec_traverse(v, visited, dflist) 427 return dflist 428 429 def bfs_traverse(self, start_label: str): 430 """ 431 Return a list of vertices through breadth first traversal starting at a given vertex 432 Args: 433 start_label (str): The starting vertex label. 434 Returns: 435 A list of vertex labels. 436 """ 437 visited = set() 438 q = Queue() 439 bflist = [] 440 441 q.enqueue(start_label) 442 visited.add(start_label) 443 bflist.append(start_label) 444 445 while len(q) > 0: 446 current = q.dequeue() 447 448 for v in self[current]: 449 if v not in visited: 450 visited.add(v) 451 q.enqueue(v) 452 bflist.append(v) 453 return bflist 454 455 def dfs(self, start_label:str, end_label:str) -> str: 456 """ 457 Recursive depth first search. 458 459 Args: 460 start_label (str): The starting vertex label. 461 end_label (str): The vertex label to search for. 462 463 Returns: 464 A vertex in the graph if found, None otherwise. 465 """ 466 def dfs_rec(current: str, end: str, visited): 467 if current == end: 468 return current 469 470 visited.add(current) 471 472 for v in self.adjacents(current): 473 if v not in visited: 474 result = dfs_rec(v, end, visited) 475 if result is not None: 476 return result 477 478 return None 479 480 return dfs_rec(start_label, end_label, set()) 481 482 def bfs(self, start_label: str, end_label: str) -> str: 483 """ 484 Breadth first search. 485 486 Args: 487 start_label (str): The starting vertex label. 488 end_label (str): The vertex label to search for. 489 490 Returns: 491 A vertex in the graph if found, None otherwise. 492 """ 493 visited = set() 494 queue = Queue() 495 496 visited.add(start_label) 497 queue.enqueue(start_label) 498 499 while len(queue) > 0: 500 current = queue.dequeue() 501 502 if current == end_label: 503 return current 504 505 for v in self[current]: 506 if v not in visited: 507 visited.add(v) 508 queue.enqueue(v) 509 510 return None 511 512 def __getitem__(self, label: str): 513 """ 514 Args: 515 vertex: vertex label 516 Returns: 517 a list of adjacent vertex labels 518 """ 519 520 return self._adjacents[label] 521 522 def is_edge(self, start_label: str, end_label: str) -> bool: 523 """ 524 Return boolean if an edge exists 525 Args: 526 start_label (str): The starting vertex label. 527 end_label (str): The ending vertex label. 528 529 Returns: 530 A boolean of whether there is an edge from start to end 531 """ 532 return end_label in self[start_label] 533 534 def __len__(self): 535 """ 536 Return the number of nodes in the graph. 537 Returns: 538 int: The number of nodes in the graph. 539 """ 540 return len(self._adjacents) 541 542 def edges(self) -> list: 543 """ 544 Return a list of edges in the graph. Each edge is represented by a tuple (start, end) 545 """ 546 edges = [] 547 for start in self._adjacents.keys(): 548 for end in self.adjacents(start): 549 if start != end: 550 edges.append((start, end)) 551 return edges 552 553 def undirected_edges(self) -> list: 554 """ 555 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end) 556 """ 557 edges = [] 558 for start in self._adjacents.keys(): 559 for end in self.adjacents(start): 560 if start != end and (end, start) not in edges: 561 edges.append((start, end)) 562 return edges 563 564class AdjacencyListWeightedGraph(AdjacencyListGraph): 565 """ 566 A weighted adjacency list vertex implementation in Python 567 (allows either directed or undirected representation) 568 """ 569 def __init__(self): 570 #: hash table of vertices in graph 571 self._adjacents = {} 572 573 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 574 """ 575 Add an edge to the graph. 576 577 Args: 578 start_label: The starting vertex label. 579 end_label: The ending vertex label. 580 weight: The edge weight. 581 directed: Whether the edge is directed. 582 """ 583 if start_label not in self._adjacents: 584 self._adjacents[start_label] = {} 585 586 self._adjacents[start_label][end_label] = weight 587 588 if not directed: 589 if end_label not in self._adjacents: 590 self._adjacents[end_label] = {} 591 self._adjacents[end_label][start_label] = weight 592 593 def delete_edge(self, start_label: str, end_label: str, directed=False): 594 """ 595 Delete an edge in the graph. 596 597 Args: 598 start_label (str): Starting vertex label. 599 end_label (str): Ending vertex label. 600 directed (bool): Whether the edge is directed. 601 602 Raises: 603 KeyError: If the edge does exist. 604 """ 605 if start_label not in self._adjacents: 606 raise KeyError(f"Vertex {start_label} does not exist") 607 if end_label not in self._adjacents[start_label]: 608 raise KeyError(f"Vertex {end_label} does not exist") 609 del self._adjacents[start_label][end_label] 610 if not directed: 611 if end_label not in self._adjacents: 612 raise KeyError(f"Vertex {end_label} does not exist") 613 if start_label not in self._adjacents[end_label]: 614 raise KeyError(f"Vertex {start_label} does not exist") 615 del self._adjacents[end_label][start_label] 616 617 def adjacents(self, vertex: str) -> list: 618 """ 619 Return a list of adjacents of a given vertex 620 Args: 621 vertex: starting vertex label 622 """ 623 return self._adjacents[vertex] 624 625 def dfs_traverse(self): 626 """ 627 Perform depth first traversal. 628 """ 629 return self._df_traverse_rec(self, set()) 630 631 def _df_traverse_rec(self, vertex, visited=None): 632 """ 633 helper depth first traversal recursive function 634 """ 635 visited.add(vertex) 636 637 for v in vertex.adjacents: 638 if v not in visited: 639 v._df_traverse_rec(v, visited) 640 641 def bfs_traverse(self): 642 """ 643 Perform breadth first traversal. 644 """ 645 start = self 646 visited = set() 647 queue = Queue() 648 649 queue.enqueue(start) 650 651 while len(queue) > 0: 652 current = queue.dequeue() 653 654 if current not in visited: 655 visited.add(current) 656 657 for v in current.adjacents: 658 queue.enqueue(v) 659 660 def dfs(self, start_label: str, end_label: str) -> str: 661 """ 662 Recursive depth first search. 663 664 Args: 665 start_label: The starting vertex label. 666 end_label: The vertex label to search for. 667 Returns: 668 vertex in the graph 669 none if not found. 670 """ 671 return self.dfs_rec(start_label, end_label, set()) 672 673 def dfs_rec(self, current, end_label, visited=None): 674 """ 675 Helper depth-first search recursive function. 676 677 Args: 678 current: Current vertex 679 end_label: Target vertex label 680 visited: Set of visited vertices 681 682 Returns: 683 vertex in the graph if found, None otherwise. 684 """ 685 if visited is None: 686 visited = set() 687 688 if current == end_label: 689 return current 690 691 visited.add(current) 692 693 for v in self.adjacents(current): 694 if v not in visited: 695 result = self.dfs_rec(v, end_label, visited) 696 if result is not None: 697 return result 698 699 return None 700 701 702 def bfs(self, start_label: str, end_label: str) -> str: 703 """ 704 Recursive breadth first search. 705 706 Args: 707 end: vertex to search for 708 709 Returns: 710 vertex in the graph 711 None if not found. 712 """ 713 visited = set() 714 queue = Queue() 715 716 visited.add(start_label) 717 queue.enqueue(start_label) 718 719 while not queue.is_empty(): 720 current = queue.dequeue() 721 722 if current == end_label: 723 return current 724 725 for v in self[current]: 726 if v not in visited: 727 visited.add(v) 728 queue.enqueue(v) 729 730 return None 731 732 def __getitem__(self, vertex: str) -> dict: 733 """ 734 Args: 735 vertex: vertex label 736 Returns: 737 Return a hashtable of adjacent vertices 738 """ 739 if vertex not in self._adjacents: 740 return {} 741 return self._adjacents[vertex] 742 743 def __len__(self): 744 """ 745 Return the number of nodes in the graph. 746 747 Returns: 748 int: The number of nodes in the graph. 749 """ 750 return len(self._adjacents) 751 752 def edges(self) -> list: 753 """ 754 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight) 755 """ 756 edges = [] 757 for start in self._adjacents.keys(): 758 for end in self.adjacents(start): 759 weight = self[start][end] 760 if start != end: 761 edges.append((start, end, weight)) 762 return edges 763 764 def undirected_edges(self) -> list: 765 """ 766 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight) 767 """ 768 edges = [] 769 for start in self._adjacents.keys(): 770 for end in self.adjacents(start): 771 weight = self[start][end] 772 if start != end and (end, start, weight) not in edges: 773 edges.append((start, end, weight)) 774 return edges 775 776 def is_edge(self, start_label: str, end_label: str) -> bool: 777 """ 778 Return boolean if an edge exists 779 Args: 780 start_label (str): starting vertex label 781 end_label (str): starting vertex label 782 783 Returns: 784 A boolean of whether there is an edge from start to end. 785 """ 786 return end_label in self[start_label] 787 788 789 790__pdoc__ = { 791 'AdjacencyMatrixGraph.add_vertex': False, 792 'AdjacencyMatrixGraph.delete_vertex': False, 793 'AdjacencyListGraph.add_directed_edge': False, 794}
6class AdjacencyMatrixGraph: 7 """ 8 An unweighted adjacency matrix graph implementation. 9 10 This class allows either directed or undirected representation of a graph. 11 Vertex labels are string types. 12 """ 13 def __init__(self, labels: list[str]): 14 """ 15 Initialize the graph with a list of vertex labels. 16 17 Args: 18 labels (list[str]): List of labels for each vertex. 19 """ 20 self.labels = labels 21 self.label_index = { label: index for index, label in enumerate(labels) } 22 23 node_count = len(self.labels) 24 self.array = [[None for i in range(node_count)] for j in range(node_count)] 25 26 def add_edge(self, start_label: str, end_label: str, directed=False): 27 """ 28 Add an edge in the graph. 29 30 Args: 31 start_label (str): Starting vertex label. 32 end_label (str): Ending vertex label. 33 directed (bool): Whether the edge is directed. 34 """ 35 a = self.label_index[start_label] 36 b = self.label_index[end_label] 37 self.array[a][b] = True 38 if not directed: 39 self.array[b][a] = True 40 41 def add_vertex(self, label: str): 42 """ 43 Add a vertex to the graph. 44 45 Args: 46 label (str): The vertex label to add. 47 """ 48 self.labels.append(label) 49 self.label_index[label] = len(self.labels) - 1 50 for row in self.array: 51 row.append(None) 52 self.array.append([None for i in range(len(self.labels))]) 53 54 def delete_vertex(self, label: str): 55 """ 56 Delete a vertex from the graph. 57 58 Args: 59 label (str): The vertex label to delete. 60 """ 61 index = self.label_index[label] 62 self.labels.pop(index) 63 self.label_index = { label: index for index, label in enumerate(self.labels) } 64 self.array.pop(index) 65 for row in self.array: 66 row.pop(index) 67 68 def delete_edge(self, start_label: str, end_label: str, directed=False): 69 """ 70 Delete an edge in the graph. 71 72 Args: 73 start_label (str): Starting vertex label. 74 end_label (str): Ending vertex label. 75 directed (bool): Whether the edge is directed. 76 """ 77 a = self.label_index[start_label] 78 b = self.label_index[end_label] 79 if self.array[a][b] is None: 80 raise KeyError(f"Edge {start_label} to {end_label} does not exist") 81 82 self.array[a][b] = None 83 if not directed: 84 if self.array[b][a] is None: 85 raise KeyError(f"Edge {end_label} to {start_label} does not exist") 86 self.array[b][a] = None 87 88 def dfs_traverse(self, start_label: str): 89 """ 90 Perform depth first traversal in an adjacency matrix 91 92 Args: 93 start_label (str): Starting vertex label. 94 95 Returns: 96 Array with depth first order traversal. 97 """ 98 return self._df_rec_traverse(start_label, set(), []) 99 100 def _df_rec_traverse(self, start_label: str, visited, dfs): 101 """ 102 Helper method for depth first recursive traversal 103 """ 104 start_index = self.label_index[start_label] 105 visited.add(start_label) 106 dfs.append(self.labels[start_index]) 107 108 for adj_index, is_connected in enumerate(self.array[start_index]): 109 adj_label = self.labels[adj_index] 110 if is_connected and adj_label not in visited: 111 self._df_rec_traverse(adj_label, visited, dfs) 112 113 return dfs 114 115 def bfs_traverse(self, start_label: str): 116 """ 117 Perform breadth first traversal in an adjacency matrix. 118 119 Args: 120 start_label (str): Starting vertex label. 121 122 Returns: 123 Array with breadth first order traversal. 124 """ 125 bfs = [] 126 q = Queue() 127 visited = set() 128 129 start_index = self.label_index[start_label] 130 q.enqueue(start_index) 131 bfs.append(self.labels[start_index]) 132 while not q.is_empty(): 133 current = q.dequeue() 134 for i in range(len(self.array)): 135 if start_index != i and self.array[current][i] and (i not in visited): 136 visited.add(i) 137 q.enqueue(i) 138 bfs.append(self.labels[i]) 139 return bfs 140 141 def vertices(self) -> list: 142 """ 143 Return a list of vertex labels of the graph 144 """ 145 return self.labels 146 147 def edges(self) -> list: 148 """ 149 Return a list of edges in the graph. Each edge is represented by a tuple (start, end) 150 """ 151 edges = [] 152 vertex_count = len(self.labels) 153 for i in range(vertex_count): 154 for j in range(vertex_count): 155 if self.array[i][j] and i != j: 156 edges.append((self.labels[i], self.labels[j])) 157 158 return edges 159 160 def undirected_edges(self) -> list: 161 """ 162 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end) 163 """ 164 edges = [] 165 vertex_count = len(self.labels) 166 for i in range(vertex_count): 167 for j in range(vertex_count): 168 if self.array[i][j] and i != j and (self.labels[j], self.labels[i]) not in edges: 169 edges.append((self.labels[i], self.labels[j])) 170 171 return edges 172 173 def is_edge(self, start_label: str, end_label: str) -> bool: 174 """ 175 Return boolean if an edge exists. 176 177 Args: 178 start_label (str): starting vertex label 179 end_label (str): starting vertex label 180 181 Returns:0 182 A boolean of whether there is an edge from start to end. 183 """ 184 start_index = self.label_index[start_label] 185 end_index = self.label_index[end_label] 186 187 return self.array[start_index][end_index] is not None 188 189 def __getitem__(self, vertex: str) -> list: 190 """ 191 Args: 192 vertex (str): The vertex label. 193 Returns: 194 A list of adjacent vertex labels. 195 """ 196 index = self.label_index[vertex] 197 return [self.labels[i] for i in range(len(self.array[index])) if self.array[index][i]] 198 199 def print_graph(self): 200 """ 201 Print the contents of the graph. 202 """ 203 print(" |", end="") 204 for label in self.labels: 205 print(f"{label:^3}", end=" ") 206 print() 207 print("----" * (len(self.array) + 1)) 208 for r, row in enumerate(self.array): 209 label = self.labels[r] 210 print(f"{label:^3}|", end="") 211 for col in row: 212 b = " T " if col else " " 213 print(b, end=" ") 214 print()
An unweighted adjacency matrix graph implementation.
This class allows either directed or undirected representation of a graph. Vertex labels are string types.
13 def __init__(self, labels: list[str]): 14 """ 15 Initialize the graph with a list of vertex labels. 16 17 Args: 18 labels (list[str]): List of labels for each vertex. 19 """ 20 self.labels = labels 21 self.label_index = { label: index for index, label in enumerate(labels) } 22 23 node_count = len(self.labels) 24 self.array = [[None for i in range(node_count)] for j in range(node_count)]
Initialize the graph with a list of vertex labels.
Args: labels (list[str]): List of labels for each vertex.
26 def add_edge(self, start_label: str, end_label: str, directed=False): 27 """ 28 Add an edge in the graph. 29 30 Args: 31 start_label (str): Starting vertex label. 32 end_label (str): Ending vertex label. 33 directed (bool): Whether the edge is directed. 34 """ 35 a = self.label_index[start_label] 36 b = self.label_index[end_label] 37 self.array[a][b] = True 38 if not directed: 39 self.array[b][a] = True
Add an edge in the graph.
Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label. directed (bool): Whether the edge is directed.
41 def add_vertex(self, label: str): 42 """ 43 Add a vertex to the graph. 44 45 Args: 46 label (str): The vertex label to add. 47 """ 48 self.labels.append(label) 49 self.label_index[label] = len(self.labels) - 1 50 for row in self.array: 51 row.append(None) 52 self.array.append([None for i in range(len(self.labels))])
Add a vertex to the graph.
Args: label (str): The vertex label to add.
54 def delete_vertex(self, label: str): 55 """ 56 Delete a vertex from the graph. 57 58 Args: 59 label (str): The vertex label to delete. 60 """ 61 index = self.label_index[label] 62 self.labels.pop(index) 63 self.label_index = { label: index for index, label in enumerate(self.labels) } 64 self.array.pop(index) 65 for row in self.array: 66 row.pop(index)
Delete a vertex from the graph.
Args: label (str): The vertex label to delete.
68 def delete_edge(self, start_label: str, end_label: str, directed=False): 69 """ 70 Delete an edge in the graph. 71 72 Args: 73 start_label (str): Starting vertex label. 74 end_label (str): Ending vertex label. 75 directed (bool): Whether the edge is directed. 76 """ 77 a = self.label_index[start_label] 78 b = self.label_index[end_label] 79 if self.array[a][b] is None: 80 raise KeyError(f"Edge {start_label} to {end_label} does not exist") 81 82 self.array[a][b] = None 83 if not directed: 84 if self.array[b][a] is None: 85 raise KeyError(f"Edge {end_label} to {start_label} does not exist") 86 self.array[b][a] = None
Delete an edge in the graph.
Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label. directed (bool): Whether the edge is directed.
88 def dfs_traverse(self, start_label: str): 89 """ 90 Perform depth first traversal in an adjacency matrix 91 92 Args: 93 start_label (str): Starting vertex label. 94 95 Returns: 96 Array with depth first order traversal. 97 """ 98 return self._df_rec_traverse(start_label, set(), [])
Perform depth first traversal in an adjacency matrix
Args: start_label (str): Starting vertex label.
Returns: Array with depth first order traversal.
115 def bfs_traverse(self, start_label: str): 116 """ 117 Perform breadth first traversal in an adjacency matrix. 118 119 Args: 120 start_label (str): Starting vertex label. 121 122 Returns: 123 Array with breadth first order traversal. 124 """ 125 bfs = [] 126 q = Queue() 127 visited = set() 128 129 start_index = self.label_index[start_label] 130 q.enqueue(start_index) 131 bfs.append(self.labels[start_index]) 132 while not q.is_empty(): 133 current = q.dequeue() 134 for i in range(len(self.array)): 135 if start_index != i and self.array[current][i] and (i not in visited): 136 visited.add(i) 137 q.enqueue(i) 138 bfs.append(self.labels[i]) 139 return bfs
Perform breadth first traversal in an adjacency matrix.
Args: start_label (str): Starting vertex label.
Returns: Array with breadth first order traversal.
141 def vertices(self) -> list: 142 """ 143 Return a list of vertex labels of the graph 144 """ 145 return self.labels
Return a list of vertex labels of the graph
147 def edges(self) -> list: 148 """ 149 Return a list of edges in the graph. Each edge is represented by a tuple (start, end) 150 """ 151 edges = [] 152 vertex_count = len(self.labels) 153 for i in range(vertex_count): 154 for j in range(vertex_count): 155 if self.array[i][j] and i != j: 156 edges.append((self.labels[i], self.labels[j])) 157 158 return edges
Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
160 def undirected_edges(self) -> list: 161 """ 162 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end) 163 """ 164 edges = [] 165 vertex_count = len(self.labels) 166 for i in range(vertex_count): 167 for j in range(vertex_count): 168 if self.array[i][j] and i != j and (self.labels[j], self.labels[i]) not in edges: 169 edges.append((self.labels[i], self.labels[j])) 170 171 return edges
Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
173 def is_edge(self, start_label: str, end_label: str) -> bool: 174 """ 175 Return boolean if an edge exists. 176 177 Args: 178 start_label (str): starting vertex label 179 end_label (str): starting vertex label 180 181 Returns:0 182 A boolean of whether there is an edge from start to end. 183 """ 184 start_index = self.label_index[start_label] 185 end_index = self.label_index[end_label] 186 187 return self.array[start_index][end_index] is not None
Return boolean if an edge exists.
Args: start_label (str): starting vertex label end_label (str): starting vertex label
Returns:0 A boolean of whether there is an edge from start to end.
199 def print_graph(self): 200 """ 201 Print the contents of the graph. 202 """ 203 print(" |", end="") 204 for label in self.labels: 205 print(f"{label:^3}", end=" ") 206 print() 207 print("----" * (len(self.array) + 1)) 208 for r, row in enumerate(self.array): 209 label = self.labels[r] 210 print(f"{label:^3}|", end="") 211 for col in row: 212 b = " T " if col else " " 213 print(b, end=" ") 214 print()
Print the contents of the graph.
216class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph): 217 """ 218 A weighted adjacency matrix graph implementation 219 (allows either directed or undirected representation) 220 vertex labels are string types 221 """ 222 def __init__(self, labels): 223 """ 224 Args: 225 labels: list of labels for each vertex (string types) 226 """ 227 super().__init__(labels) 228 229 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 230 """ 231 Add an edge to the graph. 232 233 Args: 234 start_label (str): The starting vertex label. 235 end_label (str): The ending vertex label. 236 weight: The weight of the vertex. 237 directed: Whether the edge is directed. 238 """ 239 a = self.label_index[start_label] 240 b = self.label_index[end_label] 241 242 self.array[a][b] = weight 243 self.array[a][a] = 0 244 self.array[b][b] = 0 245 246 if not directed: 247 self.array[b][a] = weight 248 249 def print_graph(self): 250 """ 251 Print the contents of the graph. 252 """ 253 print(" |", end="") 254 for label in self.labels: 255 print(f"{label:>3}", end=" ") 256 print() 257 print("----" * (len(self.array) + 1)) 258 for r, row in enumerate(self.array): 259 label = self.labels[r] 260 print(f"{label:^3}|", end="") 261 for col in row: 262 w = f"{col:3}" if col is not None else " " 263 print(w, end=" ") 264 print() 265 266 def edges(self) -> list: 267 """ 268 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight). 269 """ 270 edges = [] 271 vertex_count = len(self.labels) 272 for i in range(vertex_count): 273 for j in range(vertex_count): 274 weight = self.array[i][j] 275 if weight and i != j: 276 edges.append((self.labels[i], self.labels[j], weight)) 277 278 return edges 279 280 def undirected_edges(self) -> list: 281 """ 282 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight). 283 """ 284 edges = [] 285 vertex_count = len(self.labels) 286 for i in range(vertex_count): 287 for j in range(vertex_count): 288 weight = self.array[i][j] 289 if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges: 290 edges.append((self.labels[i], self.labels[j], weight)) 291 292 return edges 293 294 def is_edge(self, start_label: str, end_label: str) -> bool: 295 """ 296 Return boolean if an edge exists. 297 298 299 Args: 300 start_label (str): Starting vertex label. 301 end_label (str): Ending vertex label. 302 303 Returns: 304 A boolean of whether there is an edge from start to end. 305 """ 306 return super().is_edge(start_label, end_label) 307 308 def weightx(self, start: str, end: str) -> bool: 309 """ 310 Return weight of an edge (is this used???) 311 Args: 312 start_label: starting vertex label 313 end_label: starting vertex label 314 315 Returns: 316 weight value of an edge from start to end 317 """ 318 return super().is_edge(start, end) 319 320 def __getitem__(self, vertex: str) -> dict: 321 """ 322 Args: 323 vertex: vertex label 324 Returns: 325 a dictionary of adjacent vertex labels and weights 326 """ 327 index = self.label_index[vertex] 328 return {self.labels[i] : self.array[index][i] for i in range(len(self.array[index])) if self.array[index][i]}
A weighted adjacency matrix graph implementation (allows either directed or undirected representation) vertex labels are string types
222 def __init__(self, labels): 223 """ 224 Args: 225 labels: list of labels for each vertex (string types) 226 """ 227 super().__init__(labels)
Args: labels: list of labels for each vertex (string types)
229 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 230 """ 231 Add an edge to the graph. 232 233 Args: 234 start_label (str): The starting vertex label. 235 end_label (str): The ending vertex label. 236 weight: The weight of the vertex. 237 directed: Whether the edge is directed. 238 """ 239 a = self.label_index[start_label] 240 b = self.label_index[end_label] 241 242 self.array[a][b] = weight 243 self.array[a][a] = 0 244 self.array[b][b] = 0 245 246 if not directed: 247 self.array[b][a] = weight
Add an edge to the graph.
Args: start_label (str): The starting vertex label. end_label (str): The ending vertex label. weight: The weight of the vertex. directed: Whether the edge is directed.
249 def print_graph(self): 250 """ 251 Print the contents of the graph. 252 """ 253 print(" |", end="") 254 for label in self.labels: 255 print(f"{label:>3}", end=" ") 256 print() 257 print("----" * (len(self.array) + 1)) 258 for r, row in enumerate(self.array): 259 label = self.labels[r] 260 print(f"{label:^3}|", end="") 261 for col in row: 262 w = f"{col:3}" if col is not None else " " 263 print(w, end=" ") 264 print()
Print the contents of the graph.
266 def edges(self) -> list: 267 """ 268 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight). 269 """ 270 edges = [] 271 vertex_count = len(self.labels) 272 for i in range(vertex_count): 273 for j in range(vertex_count): 274 weight = self.array[i][j] 275 if weight and i != j: 276 edges.append((self.labels[i], self.labels[j], weight)) 277 278 return edges
Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
280 def undirected_edges(self) -> list: 281 """ 282 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight). 283 """ 284 edges = [] 285 vertex_count = len(self.labels) 286 for i in range(vertex_count): 287 for j in range(vertex_count): 288 weight = self.array[i][j] 289 if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges: 290 edges.append((self.labels[i], self.labels[j], weight)) 291 292 return edges
Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
294 def is_edge(self, start_label: str, end_label: str) -> bool: 295 """ 296 Return boolean if an edge exists. 297 298 299 Args: 300 start_label (str): Starting vertex label. 301 end_label (str): Ending vertex label. 302 303 Returns: 304 A boolean of whether there is an edge from start to end. 305 """ 306 return super().is_edge(start_label, end_label)
Return boolean if an edge exists.
Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label.
Returns: A boolean of whether there is an edge from start to end.
308 def weightx(self, start: str, end: str) -> bool: 309 """ 310 Return weight of an edge (is this used???) 311 Args: 312 start_label: starting vertex label 313 end_label: starting vertex label 314 315 Returns: 316 weight value of an edge from start to end 317 """ 318 return super().is_edge(start, end)
Return weight of an edge (is this used???) Args: start_label: starting vertex label end_label: starting vertex label
Returns: weight value of an edge from start to end
Inherited Members
330class AdjacencyListGraph: 331 """ 332 A unweighted adjacency list vertex implementation 333 (allows either directed or undirected representation) 334 vertex labels are string types 335 """ 336 def __init__(self): 337 #: hash table of vertices in graph 338 self._adjacents = {} 339 340 def add_edge(self, start_label: str, end_label: str, directed=False): 341 """ 342 Add an edge to the graph. 343 344 Args: 345 start_label (str): The label of the starting vertex. 346 end_label (str): The label of the ending vertex. 347 directed (bool): Whether the edge is directed. 348 """ 349 self.add_directed_edge(start_label, end_label) 350 if not directed: 351 self.add_directed_edge(end_label, start_label) 352 353 def add_directed_edge(self, start_label: str, end_label: str): 354 """ 355 Add a directed edge to the graph. 356 357 Args: 358 start_label (str): The label of the starting vertex. 359 end_label (str): The label of the ending vertex. 360 """ 361 if start_label not in self._adjacents: 362 self._adjacents[start_label] = [end_label] 363 else: 364 if end_label not in self._adjacents[start_label]: 365 self._adjacents[start_label].append(end_label) 366 if end_label not in self._adjacents: 367 self._adjacents[end_label] = [] 368 369 def delete_edge(self, start_label: str, end_label: str, directed=False): 370 """ 371 Delete an edge in the graph. 372 373 Args: 374 start_label (str): Starting vertex label. 375 end_label (str): Ending vertex label. 376 directed (bool): Whether the edge is directed. 377 Raises: 378 KeyError: If the edge does exist. 379 """ 380 if start_label not in self._adjacents: 381 raise KeyError(f"Vertex {start_label} does not exist") 382 if end_label not in self._adjacents[start_label]: 383 raise KeyError(f"Vertex {end_label} does not exist") 384 self._adjacents[start_label].remove(end_label) 385 if not directed: 386 if end_label not in self._adjacents: 387 raise KeyError(f"Vertex {end_label} does not exist") 388 if start_label not in self._adjacents[end_label]: 389 raise KeyError(f"Vertex {start_label} does not exist") 390 self._adjacents[end_label].remove(start_label) 391 392 def vertices(self) -> list: 393 """ 394 Return a list of vertex labels of the graph 395 """ 396 return list(self._adjacents.keys()) 397 398 def adjacents(self, vertex: str) -> list: 399 """ 400 Return a list of adjacents of a given vertex 401 402 Args: 403 vertex (str): The label of the vertex. 404 """ 405 return self._adjacents[vertex] 406 407 def dfs_traverse(self, start_label: str): 408 """ 409 Return a list of vertices through depth first traversal starting at a given vertex. 410 411 Args: 412 start_label (str): The starting vertex label. 413 Returns: 414 A list of vertex labels. 415 """ 416 return self._df_rec_traverse(start_label, set(), []) 417 418 def _df_rec_traverse(self, start_label: str, visited, dflist): 419 """ 420 Helper depth first traversal recursive function. 421 """ 422 visited.add(start_label) 423 dflist.append(start_label) 424 425 for v in self[start_label]: 426 if v not in visited: 427 self._df_rec_traverse(v, visited, dflist) 428 return dflist 429 430 def bfs_traverse(self, start_label: str): 431 """ 432 Return a list of vertices through breadth first traversal starting at a given vertex 433 Args: 434 start_label (str): The starting vertex label. 435 Returns: 436 A list of vertex labels. 437 """ 438 visited = set() 439 q = Queue() 440 bflist = [] 441 442 q.enqueue(start_label) 443 visited.add(start_label) 444 bflist.append(start_label) 445 446 while len(q) > 0: 447 current = q.dequeue() 448 449 for v in self[current]: 450 if v not in visited: 451 visited.add(v) 452 q.enqueue(v) 453 bflist.append(v) 454 return bflist 455 456 def dfs(self, start_label:str, end_label:str) -> str: 457 """ 458 Recursive depth first search. 459 460 Args: 461 start_label (str): The starting vertex label. 462 end_label (str): The vertex label to search for. 463 464 Returns: 465 A vertex in the graph if found, None otherwise. 466 """ 467 def dfs_rec(current: str, end: str, visited): 468 if current == end: 469 return current 470 471 visited.add(current) 472 473 for v in self.adjacents(current): 474 if v not in visited: 475 result = dfs_rec(v, end, visited) 476 if result is not None: 477 return result 478 479 return None 480 481 return dfs_rec(start_label, end_label, set()) 482 483 def bfs(self, start_label: str, end_label: str) -> str: 484 """ 485 Breadth first search. 486 487 Args: 488 start_label (str): The starting vertex label. 489 end_label (str): The vertex label to search for. 490 491 Returns: 492 A vertex in the graph if found, None otherwise. 493 """ 494 visited = set() 495 queue = Queue() 496 497 visited.add(start_label) 498 queue.enqueue(start_label) 499 500 while len(queue) > 0: 501 current = queue.dequeue() 502 503 if current == end_label: 504 return current 505 506 for v in self[current]: 507 if v not in visited: 508 visited.add(v) 509 queue.enqueue(v) 510 511 return None 512 513 def __getitem__(self, label: str): 514 """ 515 Args: 516 vertex: vertex label 517 Returns: 518 a list of adjacent vertex labels 519 """ 520 521 return self._adjacents[label] 522 523 def is_edge(self, start_label: str, end_label: str) -> bool: 524 """ 525 Return boolean if an edge exists 526 Args: 527 start_label (str): The starting vertex label. 528 end_label (str): The ending vertex label. 529 530 Returns: 531 A boolean of whether there is an edge from start to end 532 """ 533 return end_label in self[start_label] 534 535 def __len__(self): 536 """ 537 Return the number of nodes in the graph. 538 Returns: 539 int: The number of nodes in the graph. 540 """ 541 return len(self._adjacents) 542 543 def edges(self) -> list: 544 """ 545 Return a list of edges in the graph. Each edge is represented by a tuple (start, end) 546 """ 547 edges = [] 548 for start in self._adjacents.keys(): 549 for end in self.adjacents(start): 550 if start != end: 551 edges.append((start, end)) 552 return edges 553 554 def undirected_edges(self) -> list: 555 """ 556 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end) 557 """ 558 edges = [] 559 for start in self._adjacents.keys(): 560 for end in self.adjacents(start): 561 if start != end and (end, start) not in edges: 562 edges.append((start, end)) 563 return edges
A unweighted adjacency list vertex implementation (allows either directed or undirected representation) vertex labels are string types
340 def add_edge(self, start_label: str, end_label: str, directed=False): 341 """ 342 Add an edge to the graph. 343 344 Args: 345 start_label (str): The label of the starting vertex. 346 end_label (str): The label of the ending vertex. 347 directed (bool): Whether the edge is directed. 348 """ 349 self.add_directed_edge(start_label, end_label) 350 if not directed: 351 self.add_directed_edge(end_label, start_label)
Add an edge to the graph.
Args: start_label (str): The label of the starting vertex. end_label (str): The label of the ending vertex. directed (bool): Whether the edge is directed.
353 def add_directed_edge(self, start_label: str, end_label: str): 354 """ 355 Add a directed edge to the graph. 356 357 Args: 358 start_label (str): The label of the starting vertex. 359 end_label (str): The label of the ending vertex. 360 """ 361 if start_label not in self._adjacents: 362 self._adjacents[start_label] = [end_label] 363 else: 364 if end_label not in self._adjacents[start_label]: 365 self._adjacents[start_label].append(end_label) 366 if end_label not in self._adjacents: 367 self._adjacents[end_label] = []
Add a directed edge to the graph.
Args: start_label (str): The label of the starting vertex. end_label (str): The label of the ending vertex.
369 def delete_edge(self, start_label: str, end_label: str, directed=False): 370 """ 371 Delete an edge in the graph. 372 373 Args: 374 start_label (str): Starting vertex label. 375 end_label (str): Ending vertex label. 376 directed (bool): Whether the edge is directed. 377 Raises: 378 KeyError: If the edge does exist. 379 """ 380 if start_label not in self._adjacents: 381 raise KeyError(f"Vertex {start_label} does not exist") 382 if end_label not in self._adjacents[start_label]: 383 raise KeyError(f"Vertex {end_label} does not exist") 384 self._adjacents[start_label].remove(end_label) 385 if not directed: 386 if end_label not in self._adjacents: 387 raise KeyError(f"Vertex {end_label} does not exist") 388 if start_label not in self._adjacents[end_label]: 389 raise KeyError(f"Vertex {start_label} does not exist") 390 self._adjacents[end_label].remove(start_label)
Delete an edge in the graph.
Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label. directed (bool): Whether the edge is directed. Raises: KeyError: If the edge does exist.
392 def vertices(self) -> list: 393 """ 394 Return a list of vertex labels of the graph 395 """ 396 return list(self._adjacents.keys())
Return a list of vertex labels of the graph
398 def adjacents(self, vertex: str) -> list: 399 """ 400 Return a list of adjacents of a given vertex 401 402 Args: 403 vertex (str): The label of the vertex. 404 """ 405 return self._adjacents[vertex]
Return a list of adjacents of a given vertex
Args: vertex (str): The label of the vertex.
407 def dfs_traverse(self, start_label: str): 408 """ 409 Return a list of vertices through depth first traversal starting at a given vertex. 410 411 Args: 412 start_label (str): The starting vertex label. 413 Returns: 414 A list of vertex labels. 415 """ 416 return self._df_rec_traverse(start_label, set(), [])
Return a list of vertices through depth first traversal starting at a given vertex.
Args: start_label (str): The starting vertex label. Returns: A list of vertex labels.
430 def bfs_traverse(self, start_label: str): 431 """ 432 Return a list of vertices through breadth first traversal starting at a given vertex 433 Args: 434 start_label (str): The starting vertex label. 435 Returns: 436 A list of vertex labels. 437 """ 438 visited = set() 439 q = Queue() 440 bflist = [] 441 442 q.enqueue(start_label) 443 visited.add(start_label) 444 bflist.append(start_label) 445 446 while len(q) > 0: 447 current = q.dequeue() 448 449 for v in self[current]: 450 if v not in visited: 451 visited.add(v) 452 q.enqueue(v) 453 bflist.append(v) 454 return bflist
Return a list of vertices through breadth first traversal starting at a given vertex Args: start_label (str): The starting vertex label. Returns: A list of vertex labels.
456 def dfs(self, start_label:str, end_label:str) -> str: 457 """ 458 Recursive depth first search. 459 460 Args: 461 start_label (str): The starting vertex label. 462 end_label (str): The vertex label to search for. 463 464 Returns: 465 A vertex in the graph if found, None otherwise. 466 """ 467 def dfs_rec(current: str, end: str, visited): 468 if current == end: 469 return current 470 471 visited.add(current) 472 473 for v in self.adjacents(current): 474 if v not in visited: 475 result = dfs_rec(v, end, visited) 476 if result is not None: 477 return result 478 479 return None 480 481 return dfs_rec(start_label, end_label, set())
Recursive depth first search.
Args: start_label (str): The starting vertex label. end_label (str): The vertex label to search for.
Returns: A vertex in the graph if found, None otherwise.
483 def bfs(self, start_label: str, end_label: str) -> str: 484 """ 485 Breadth first search. 486 487 Args: 488 start_label (str): The starting vertex label. 489 end_label (str): The vertex label to search for. 490 491 Returns: 492 A vertex in the graph if found, None otherwise. 493 """ 494 visited = set() 495 queue = Queue() 496 497 visited.add(start_label) 498 queue.enqueue(start_label) 499 500 while len(queue) > 0: 501 current = queue.dequeue() 502 503 if current == end_label: 504 return current 505 506 for v in self[current]: 507 if v not in visited: 508 visited.add(v) 509 queue.enqueue(v) 510 511 return None
Breadth first search.
Args: start_label (str): The starting vertex label. end_label (str): The vertex label to search for.
Returns: A vertex in the graph if found, None otherwise.
523 def is_edge(self, start_label: str, end_label: str) -> bool: 524 """ 525 Return boolean if an edge exists 526 Args: 527 start_label (str): The starting vertex label. 528 end_label (str): The ending vertex label. 529 530 Returns: 531 A boolean of whether there is an edge from start to end 532 """ 533 return end_label in self[start_label]
Return boolean if an edge exists Args: start_label (str): The starting vertex label. end_label (str): The ending vertex label.
Returns: A boolean of whether there is an edge from start to end
543 def edges(self) -> list: 544 """ 545 Return a list of edges in the graph. Each edge is represented by a tuple (start, end) 546 """ 547 edges = [] 548 for start in self._adjacents.keys(): 549 for end in self.adjacents(start): 550 if start != end: 551 edges.append((start, end)) 552 return edges
Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
554 def undirected_edges(self) -> list: 555 """ 556 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end) 557 """ 558 edges = [] 559 for start in self._adjacents.keys(): 560 for end in self.adjacents(start): 561 if start != end and (end, start) not in edges: 562 edges.append((start, end)) 563 return edges
Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
565class AdjacencyListWeightedGraph(AdjacencyListGraph): 566 """ 567 A weighted adjacency list vertex implementation in Python 568 (allows either directed or undirected representation) 569 """ 570 def __init__(self): 571 #: hash table of vertices in graph 572 self._adjacents = {} 573 574 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 575 """ 576 Add an edge to the graph. 577 578 Args: 579 start_label: The starting vertex label. 580 end_label: The ending vertex label. 581 weight: The edge weight. 582 directed: Whether the edge is directed. 583 """ 584 if start_label not in self._adjacents: 585 self._adjacents[start_label] = {} 586 587 self._adjacents[start_label][end_label] = weight 588 589 if not directed: 590 if end_label not in self._adjacents: 591 self._adjacents[end_label] = {} 592 self._adjacents[end_label][start_label] = weight 593 594 def delete_edge(self, start_label: str, end_label: str, directed=False): 595 """ 596 Delete an edge in the graph. 597 598 Args: 599 start_label (str): Starting vertex label. 600 end_label (str): Ending vertex label. 601 directed (bool): Whether the edge is directed. 602 603 Raises: 604 KeyError: If the edge does exist. 605 """ 606 if start_label not in self._adjacents: 607 raise KeyError(f"Vertex {start_label} does not exist") 608 if end_label not in self._adjacents[start_label]: 609 raise KeyError(f"Vertex {end_label} does not exist") 610 del self._adjacents[start_label][end_label] 611 if not directed: 612 if end_label not in self._adjacents: 613 raise KeyError(f"Vertex {end_label} does not exist") 614 if start_label not in self._adjacents[end_label]: 615 raise KeyError(f"Vertex {start_label} does not exist") 616 del self._adjacents[end_label][start_label] 617 618 def adjacents(self, vertex: str) -> list: 619 """ 620 Return a list of adjacents of a given vertex 621 Args: 622 vertex: starting vertex label 623 """ 624 return self._adjacents[vertex] 625 626 def dfs_traverse(self): 627 """ 628 Perform depth first traversal. 629 """ 630 return self._df_traverse_rec(self, set()) 631 632 def _df_traverse_rec(self, vertex, visited=None): 633 """ 634 helper depth first traversal recursive function 635 """ 636 visited.add(vertex) 637 638 for v in vertex.adjacents: 639 if v not in visited: 640 v._df_traverse_rec(v, visited) 641 642 def bfs_traverse(self): 643 """ 644 Perform breadth first traversal. 645 """ 646 start = self 647 visited = set() 648 queue = Queue() 649 650 queue.enqueue(start) 651 652 while len(queue) > 0: 653 current = queue.dequeue() 654 655 if current not in visited: 656 visited.add(current) 657 658 for v in current.adjacents: 659 queue.enqueue(v) 660 661 def dfs(self, start_label: str, end_label: str) -> str: 662 """ 663 Recursive depth first search. 664 665 Args: 666 start_label: The starting vertex label. 667 end_label: The vertex label to search for. 668 Returns: 669 vertex in the graph 670 none if not found. 671 """ 672 return self.dfs_rec(start_label, end_label, set()) 673 674 def dfs_rec(self, current, end_label, visited=None): 675 """ 676 Helper depth-first search recursive function. 677 678 Args: 679 current: Current vertex 680 end_label: Target vertex label 681 visited: Set of visited vertices 682 683 Returns: 684 vertex in the graph if found, None otherwise. 685 """ 686 if visited is None: 687 visited = set() 688 689 if current == end_label: 690 return current 691 692 visited.add(current) 693 694 for v in self.adjacents(current): 695 if v not in visited: 696 result = self.dfs_rec(v, end_label, visited) 697 if result is not None: 698 return result 699 700 return None 701 702 703 def bfs(self, start_label: str, end_label: str) -> str: 704 """ 705 Recursive breadth first search. 706 707 Args: 708 end: vertex to search for 709 710 Returns: 711 vertex in the graph 712 None if not found. 713 """ 714 visited = set() 715 queue = Queue() 716 717 visited.add(start_label) 718 queue.enqueue(start_label) 719 720 while not queue.is_empty(): 721 current = queue.dequeue() 722 723 if current == end_label: 724 return current 725 726 for v in self[current]: 727 if v not in visited: 728 visited.add(v) 729 queue.enqueue(v) 730 731 return None 732 733 def __getitem__(self, vertex: str) -> dict: 734 """ 735 Args: 736 vertex: vertex label 737 Returns: 738 Return a hashtable of adjacent vertices 739 """ 740 if vertex not in self._adjacents: 741 return {} 742 return self._adjacents[vertex] 743 744 def __len__(self): 745 """ 746 Return the number of nodes in the graph. 747 748 Returns: 749 int: The number of nodes in the graph. 750 """ 751 return len(self._adjacents) 752 753 def edges(self) -> list: 754 """ 755 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight) 756 """ 757 edges = [] 758 for start in self._adjacents.keys(): 759 for end in self.adjacents(start): 760 weight = self[start][end] 761 if start != end: 762 edges.append((start, end, weight)) 763 return edges 764 765 def undirected_edges(self) -> list: 766 """ 767 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight) 768 """ 769 edges = [] 770 for start in self._adjacents.keys(): 771 for end in self.adjacents(start): 772 weight = self[start][end] 773 if start != end and (end, start, weight) not in edges: 774 edges.append((start, end, weight)) 775 return edges 776 777 def is_edge(self, start_label: str, end_label: str) -> bool: 778 """ 779 Return boolean if an edge exists 780 Args: 781 start_label (str): starting vertex label 782 end_label (str): starting vertex label 783 784 Returns: 785 A boolean of whether there is an edge from start to end. 786 """ 787 return end_label in self[start_label]
A weighted adjacency list vertex implementation in Python (allows either directed or undirected representation)
574 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 575 """ 576 Add an edge to the graph. 577 578 Args: 579 start_label: The starting vertex label. 580 end_label: The ending vertex label. 581 weight: The edge weight. 582 directed: Whether the edge is directed. 583 """ 584 if start_label not in self._adjacents: 585 self._adjacents[start_label] = {} 586 587 self._adjacents[start_label][end_label] = weight 588 589 if not directed: 590 if end_label not in self._adjacents: 591 self._adjacents[end_label] = {} 592 self._adjacents[end_label][start_label] = weight
Add an edge to the graph.
Args: start_label: The starting vertex label. end_label: The ending vertex label. weight: The edge weight. directed: Whether the edge is directed.
594 def delete_edge(self, start_label: str, end_label: str, directed=False): 595 """ 596 Delete an edge in the graph. 597 598 Args: 599 start_label (str): Starting vertex label. 600 end_label (str): Ending vertex label. 601 directed (bool): Whether the edge is directed. 602 603 Raises: 604 KeyError: If the edge does exist. 605 """ 606 if start_label not in self._adjacents: 607 raise KeyError(f"Vertex {start_label} does not exist") 608 if end_label not in self._adjacents[start_label]: 609 raise KeyError(f"Vertex {end_label} does not exist") 610 del self._adjacents[start_label][end_label] 611 if not directed: 612 if end_label not in self._adjacents: 613 raise KeyError(f"Vertex {end_label} does not exist") 614 if start_label not in self._adjacents[end_label]: 615 raise KeyError(f"Vertex {start_label} does not exist") 616 del self._adjacents[end_label][start_label]
Delete an edge in the graph.
Args: start_label (str): Starting vertex label. end_label (str): Ending vertex label. directed (bool): Whether the edge is directed.
Raises: KeyError: If the edge does exist.
618 def adjacents(self, vertex: str) -> list: 619 """ 620 Return a list of adjacents of a given vertex 621 Args: 622 vertex: starting vertex label 623 """ 624 return self._adjacents[vertex]
Return a list of adjacents of a given vertex Args: vertex: starting vertex label
626 def dfs_traverse(self): 627 """ 628 Perform depth first traversal. 629 """ 630 return self._df_traverse_rec(self, set())
Perform depth first traversal.
642 def bfs_traverse(self): 643 """ 644 Perform breadth first traversal. 645 """ 646 start = self 647 visited = set() 648 queue = Queue() 649 650 queue.enqueue(start) 651 652 while len(queue) > 0: 653 current = queue.dequeue() 654 655 if current not in visited: 656 visited.add(current) 657 658 for v in current.adjacents: 659 queue.enqueue(v)
Perform breadth first traversal.
661 def dfs(self, start_label: str, end_label: str) -> str: 662 """ 663 Recursive depth first search. 664 665 Args: 666 start_label: The starting vertex label. 667 end_label: The vertex label to search for. 668 Returns: 669 vertex in the graph 670 none if not found. 671 """ 672 return self.dfs_rec(start_label, end_label, set())
Recursive depth first search.
Args: start_label: The starting vertex label. end_label: The vertex label to search for. Returns: vertex in the graph none if not found.
674 def dfs_rec(self, current, end_label, visited=None): 675 """ 676 Helper depth-first search recursive function. 677 678 Args: 679 current: Current vertex 680 end_label: Target vertex label 681 visited: Set of visited vertices 682 683 Returns: 684 vertex in the graph if found, None otherwise. 685 """ 686 if visited is None: 687 visited = set() 688 689 if current == end_label: 690 return current 691 692 visited.add(current) 693 694 for v in self.adjacents(current): 695 if v not in visited: 696 result = self.dfs_rec(v, end_label, visited) 697 if result is not None: 698 return result 699 700 return None
Helper depth-first search recursive function.
Args: current: Current vertex end_label: Target vertex label visited: Set of visited vertices
Returns: vertex in the graph if found, None otherwise.
703 def bfs(self, start_label: str, end_label: str) -> str: 704 """ 705 Recursive breadth first search. 706 707 Args: 708 end: vertex to search for 709 710 Returns: 711 vertex in the graph 712 None if not found. 713 """ 714 visited = set() 715 queue = Queue() 716 717 visited.add(start_label) 718 queue.enqueue(start_label) 719 720 while not queue.is_empty(): 721 current = queue.dequeue() 722 723 if current == end_label: 724 return current 725 726 for v in self[current]: 727 if v not in visited: 728 visited.add(v) 729 queue.enqueue(v) 730 731 return None
Recursive breadth first search.
Args: end: vertex to search for
Returns: vertex in the graph None if not found.
753 def edges(self) -> list: 754 """ 755 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight) 756 """ 757 edges = [] 758 for start in self._adjacents.keys(): 759 for end in self.adjacents(start): 760 weight = self[start][end] 761 if start != end: 762 edges.append((start, end, weight)) 763 return edges
Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
765 def undirected_edges(self) -> list: 766 """ 767 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight) 768 """ 769 edges = [] 770 for start in self._adjacents.keys(): 771 for end in self.adjacents(start): 772 weight = self[start][end] 773 if start != end and (end, start, weight) not in edges: 774 edges.append((start, end, weight)) 775 return edges
Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
777 def is_edge(self, start_label: str, end_label: str) -> bool: 778 """ 779 Return boolean if an edge exists 780 Args: 781 start_label (str): starting vertex label 782 end_label (str): starting vertex label 783 784 Returns: 785 A boolean of whether there is an edge from start to end. 786 """ 787 return end_label in self[start_label]
Return boolean if an edge exists Args: start_label (str): starting vertex label end_label (str): starting vertex label
Returns: A boolean of whether there is an edge from start to end.