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 __contains__(self, label: str) -> bool: 199 """ 200 Args: 201 label (str): The vertex label. 202 Returns: 203 A boolean indicating if the vertex is in the graph. 204 """ 205 return label in self.label_index 206 207 def print_graph(self): 208 """ 209 Print the contents of the graph. 210 """ 211 print(" |", end="") 212 for label in self.labels: 213 print(f"{label:^3}", end=" ") 214 print() 215 print("----" * (len(self.array) + 1)) 216 for r, row in enumerate(self.array): 217 label = self.labels[r] 218 print(f"{label:^3}|", end="") 219 for col in row: 220 b = " T " if col else " " 221 print(b, end=" ") 222 print() 223 224class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph): 225 """ 226 A weighted adjacency matrix graph implementation 227 (allows either directed or undirected representation) 228 vertex labels are string types 229 """ 230 def __init__(self, labels): 231 """ 232 Args: 233 labels: list of labels for each vertex (string types) 234 """ 235 super().__init__(labels) 236 237 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 238 """ 239 Add an edge to the graph. 240 241 Args: 242 start_label (str): The starting vertex label. 243 end_label (str): The ending vertex label. 244 weight: The weight of the vertex. 245 directed: Whether the edge is directed. 246 """ 247 a = self.label_index[start_label] 248 b = self.label_index[end_label] 249 250 self.array[a][b] = weight 251 self.array[a][a] = 0 252 self.array[b][b] = 0 253 254 if not directed: 255 self.array[b][a] = weight 256 257 def print_graph(self): 258 """ 259 Print the contents of the graph. 260 """ 261 print(" |", end="") 262 for label in self.labels: 263 print(f"{label:>3}", end=" ") 264 print() 265 print("----" * (len(self.array) + 1)) 266 for r, row in enumerate(self.array): 267 label = self.labels[r] 268 print(f"{label:^3}|", end="") 269 for col in row: 270 w = f"{col:3}" if col is not None else " " 271 print(w, end=" ") 272 print() 273 274 def edges(self) -> list: 275 """ 276 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight). 277 """ 278 edges = [] 279 vertex_count = len(self.labels) 280 for i in range(vertex_count): 281 for j in range(vertex_count): 282 weight = self.array[i][j] 283 if weight and i != j: 284 edges.append((self.labels[i], self.labels[j], weight)) 285 286 return edges 287 288 def undirected_edges(self) -> list: 289 """ 290 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight). 291 """ 292 edges = [] 293 vertex_count = len(self.labels) 294 for i in range(vertex_count): 295 for j in range(vertex_count): 296 weight = self.array[i][j] 297 if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges: 298 edges.append((self.labels[i], self.labels[j], weight)) 299 300 return edges 301 302 def is_edge(self, start_label: str, end_label: str) -> bool: 303 """ 304 Return boolean if an edge exists. 305 306 307 Args: 308 start_label (str): Starting vertex label. 309 end_label (str): Ending vertex label. 310 311 Returns: 312 A boolean of whether there is an edge from start to end. 313 """ 314 return super().is_edge(start_label, end_label) 315 316 def weightx(self, start: str, end: str) -> bool: 317 """ 318 Return weight of an edge (is this used???) 319 Args: 320 start_label: starting vertex label 321 end_label: starting vertex label 322 323 Returns: 324 weight value of an edge from start to end 325 """ 326 return super().is_edge(start, end) 327 328 def __getitem__(self, vertex: str) -> dict: 329 """ 330 Args: 331 vertex: vertex label 332 Returns: 333 a dictionary of adjacent vertex labels and weights 334 """ 335 index = self.label_index[vertex] 336 return {self.labels[i] : self.array[index][i] for i in range(len(self.array[index])) if self.array[index][i]} 337 338class AdjacencyListGraph: 339 """ 340 A unweighted adjacency list vertex implementation 341 (allows either directed or undirected representation) 342 vertex labels are string types 343 """ 344 def __init__(self): 345 #: hash table of vertices in graph 346 self._adjacents = {} 347 348 def add_edge(self, start_label: str, end_label: str, directed=False): 349 """ 350 Add an edge to the graph. 351 352 Args: 353 start_label (str): The label of the starting vertex. 354 end_label (str): The label of the ending vertex. 355 directed (bool): Whether the edge is directed. 356 """ 357 self.add_directed_edge(start_label, end_label) 358 if not directed: 359 self.add_directed_edge(end_label, start_label) 360 361 def add_directed_edge(self, start_label: str, end_label: str): 362 """ 363 Add a directed edge to the graph. 364 365 Args: 366 start_label (str): The label of the starting vertex. 367 end_label (str): The label of the ending vertex. 368 """ 369 if start_label not in self._adjacents: 370 self._adjacents[start_label] = [end_label] 371 else: 372 if end_label not in self._adjacents[start_label]: 373 self._adjacents[start_label].append(end_label) 374 if end_label not in self._adjacents: 375 self._adjacents[end_label] = [] 376 377 def delete_edge(self, start_label: str, end_label: str, directed=False): 378 """ 379 Delete an edge in the graph. 380 381 Args: 382 start_label (str): Starting vertex label. 383 end_label (str): Ending vertex label. 384 directed (bool): Whether the edge is directed. 385 Raises: 386 KeyError: If the edge does exist. 387 """ 388 if start_label not in self._adjacents: 389 raise KeyError(f"Vertex {start_label} does not exist") 390 if end_label not in self._adjacents[start_label]: 391 raise KeyError(f"Vertex {end_label} does not exist") 392 self._adjacents[start_label].remove(end_label) 393 if not directed: 394 if end_label not in self._adjacents: 395 raise KeyError(f"Vertex {end_label} does not exist") 396 if start_label not in self._adjacents[end_label]: 397 raise KeyError(f"Vertex {start_label} does not exist") 398 self._adjacents[end_label].remove(start_label) 399 400 def vertices(self) -> list: 401 """ 402 Return a list of vertex labels of the graph 403 """ 404 return list(self._adjacents.keys()) 405 406 def adjacents(self, vertex: str) -> list: 407 """ 408 Return a list of adjacents of a given vertex 409 410 Args: 411 vertex (str): The label of the vertex. 412 """ 413 return self._adjacents[vertex] 414 415 def dfs_traverse(self, start_label: str): 416 """ 417 Return a list of vertices through depth first traversal starting at a given vertex. 418 419 Args: 420 start_label (str): The starting vertex label. 421 Returns: 422 A list of vertex labels. 423 """ 424 return self._df_rec_traverse(start_label, set(), []) 425 426 def _df_rec_traverse(self, start_label: str, visited, dflist): 427 """ 428 Helper depth first traversal recursive function. 429 """ 430 visited.add(start_label) 431 dflist.append(start_label) 432 433 for v in self[start_label]: 434 if v not in visited: 435 self._df_rec_traverse(v, visited, dflist) 436 return dflist 437 438 def bfs_traverse(self, start_label: str): 439 """ 440 Return a list of vertices through breadth first traversal starting at a given vertex 441 Args: 442 start_label (str): The starting vertex label. 443 Returns: 444 A list of vertex labels. 445 """ 446 visited = set() 447 q = Queue() 448 bflist = [] 449 450 q.enqueue(start_label) 451 visited.add(start_label) 452 bflist.append(start_label) 453 454 while len(q) > 0: 455 current = q.dequeue() 456 457 for v in self[current]: 458 if v not in visited: 459 visited.add(v) 460 q.enqueue(v) 461 bflist.append(v) 462 return bflist 463 464 def dfs(self, start_label:str, end_label:str) -> str: 465 """ 466 Recursive depth first search. 467 468 Args: 469 start_label (str): The starting vertex label. 470 end_label (str): The vertex label to search for. 471 472 Returns: 473 A vertex in the graph if found, None otherwise. 474 """ 475 def dfs_rec(current: str, end: str, visited): 476 if current == end: 477 return current 478 479 visited.add(current) 480 481 for v in self.adjacents(current): 482 if v not in visited: 483 result = dfs_rec(v, end, visited) 484 if result is not None: 485 return result 486 487 return None 488 489 return dfs_rec(start_label, end_label, set()) 490 491 def bfs(self, start_label: str, end_label: str) -> str: 492 """ 493 Breadth first search. 494 495 Args: 496 start_label (str): The starting vertex label. 497 end_label (str): The vertex label to search for. 498 499 Returns: 500 A vertex in the graph if found, None otherwise. 501 """ 502 visited = set() 503 queue = Queue() 504 505 visited.add(start_label) 506 queue.enqueue(start_label) 507 508 while len(queue) > 0: 509 current = queue.dequeue() 510 511 if current == end_label: 512 return current 513 514 for v in self[current]: 515 if v not in visited: 516 visited.add(v) 517 queue.enqueue(v) 518 519 return None 520 521 def __getitem__(self, label: str): 522 """ 523 Args: 524 vertex: vertex label 525 Returns: 526 a list of adjacent vertex labels 527 """ 528 529 return self._adjacents[label] 530 531 def is_edge(self, start_label: str, end_label: str) -> bool: 532 """ 533 Return boolean if an edge exists 534 Args: 535 start_label (str): The starting vertex label. 536 end_label (str): The ending vertex label. 537 538 Returns: 539 A boolean of whether there is an edge from start to end 540 """ 541 return end_label in self[start_label] 542 543 def __len__(self): 544 """ 545 Return the number of nodes in the graph. 546 Returns: 547 int: The number of nodes in the graph. 548 """ 549 return len(self._adjacents) 550 551 def __contains__(self, label: str) -> bool: 552 """ 553 Args: 554 label (str): The vertex label. 555 Returns: 556 A boolean indicating if the vertex is in the graph. 557 """ 558 return label in self._adjacents 559 560 def edges(self) -> list: 561 """ 562 Return a list of edges in the graph. Each edge is represented by a tuple (start, end) 563 """ 564 edges = [] 565 for start in self._adjacents.keys(): 566 for end in self.adjacents(start): 567 if start != end: 568 edges.append((start, end)) 569 return edges 570 571 def undirected_edges(self) -> list: 572 """ 573 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end) 574 """ 575 edges = [] 576 for start in self._adjacents.keys(): 577 for end in self.adjacents(start): 578 if start != end and (end, start) not in edges: 579 edges.append((start, end)) 580 return edges 581 582class AdjacencyListWeightedGraph(AdjacencyListGraph): 583 """ 584 A weighted adjacency list vertex implementation in Python 585 (allows either directed or undirected representation) 586 """ 587 def __init__(self): 588 #: hash table of vertices in graph 589 self._adjacents = {} 590 591 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 592 """ 593 Add an edge to the graph. 594 595 Args: 596 start_label: The starting vertex label. 597 end_label: The ending vertex label. 598 weight: The edge weight. 599 directed: Whether the edge is directed. 600 """ 601 if start_label not in self._adjacents: 602 self._adjacents[start_label] = {} 603 604 self._adjacents[start_label][end_label] = weight 605 606 if not directed: 607 if end_label not in self._adjacents: 608 self._adjacents[end_label] = {} 609 self._adjacents[end_label][start_label] = weight 610 611 def delete_edge(self, start_label: str, end_label: str, directed=False): 612 """ 613 Delete an edge in the graph. 614 615 Args: 616 start_label (str): Starting vertex label. 617 end_label (str): Ending vertex label. 618 directed (bool): Whether the edge is directed. 619 620 Raises: 621 KeyError: If the edge does exist. 622 """ 623 if start_label not in self._adjacents: 624 raise KeyError(f"Vertex {start_label} does not exist") 625 if end_label not in self._adjacents[start_label]: 626 raise KeyError(f"Vertex {end_label} does not exist") 627 del self._adjacents[start_label][end_label] 628 if not directed: 629 if end_label not in self._adjacents: 630 raise KeyError(f"Vertex {end_label} does not exist") 631 if start_label not in self._adjacents[end_label]: 632 raise KeyError(f"Vertex {start_label} does not exist") 633 del self._adjacents[end_label][start_label] 634 635 def adjacents(self, vertex: str) -> list: 636 """ 637 Return a list of adjacents of a given vertex 638 Args: 639 vertex: starting vertex label 640 """ 641 return self._adjacents[vertex] 642 643 def dfs_traverse(self): 644 """ 645 Perform depth first traversal. 646 """ 647 return self._df_traverse_rec(self, set()) 648 649 def _df_traverse_rec(self, vertex, visited=None): 650 """ 651 helper depth first traversal recursive function 652 """ 653 visited.add(vertex) 654 655 for v in vertex.adjacents: 656 if v not in visited: 657 v._df_traverse_rec(v, visited) 658 659 def bfs_traverse(self): 660 """ 661 Perform breadth first traversal. 662 """ 663 start = self 664 visited = set() 665 queue = Queue() 666 667 queue.enqueue(start) 668 669 while len(queue) > 0: 670 current = queue.dequeue() 671 672 if current not in visited: 673 visited.add(current) 674 675 for v in current.adjacents: 676 queue.enqueue(v) 677 678 def dfs(self, start_label: str, end_label: str) -> str: 679 """ 680 Recursive depth first search. 681 682 Args: 683 start_label: The starting vertex label. 684 end_label: The vertex label to search for. 685 Returns: 686 vertex in the graph 687 none if not found. 688 """ 689 return self.dfs_rec(start_label, end_label, set()) 690 691 def dfs_rec(self, current, end_label, visited=None): 692 """ 693 Helper depth-first search recursive function. 694 695 Args: 696 current: Current vertex 697 end_label: Target vertex label 698 visited: Set of visited vertices 699 700 Returns: 701 vertex in the graph if found, None otherwise. 702 """ 703 if visited is None: 704 visited = set() 705 706 if current == end_label: 707 return current 708 709 visited.add(current) 710 711 for v in self.adjacents(current): 712 if v not in visited: 713 result = self.dfs_rec(v, end_label, visited) 714 if result is not None: 715 return result 716 717 return None 718 719 720 def bfs(self, start_label: str, end_label: str) -> str: 721 """ 722 Recursive breadth first search. 723 724 Args: 725 end: vertex to search for 726 727 Returns: 728 vertex in the graph 729 None if not found. 730 """ 731 visited = set() 732 queue = Queue() 733 734 visited.add(start_label) 735 queue.enqueue(start_label) 736 737 while not queue.is_empty(): 738 current = queue.dequeue() 739 740 if current == end_label: 741 return current 742 743 for v in self[current]: 744 if v not in visited: 745 visited.add(v) 746 queue.enqueue(v) 747 748 return None 749 750 def __getitem__(self, vertex: str) -> dict: 751 """ 752 Args: 753 vertex: vertex label 754 Returns: 755 Return a hashtable of adjacent vertices 756 """ 757 if vertex not in self._adjacents: 758 return {} 759 return self._adjacents[vertex] 760 761 def __len__(self): 762 """ 763 Return the number of nodes in the graph. 764 765 Returns: 766 int: The number of nodes in the graph. 767 """ 768 return len(self._adjacents) 769 770 def edges(self) -> list: 771 """ 772 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight) 773 """ 774 edges = [] 775 for start in self._adjacents.keys(): 776 for end in self.adjacents(start): 777 weight = self[start][end] 778 if start != end: 779 edges.append((start, end, weight)) 780 return edges 781 782 def undirected_edges(self) -> list: 783 """ 784 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight) 785 """ 786 edges = [] 787 for start in self._adjacents.keys(): 788 for end in self.adjacents(start): 789 weight = self[start][end] 790 if start != end and (end, start, weight) not in edges: 791 edges.append((start, end, weight)) 792 return edges 793 794 def is_edge(self, start_label: str, end_label: str) -> bool: 795 """ 796 Return boolean if an edge exists 797 Args: 798 start_label (str): starting vertex label 799 end_label (str): starting vertex label 800 801 Returns: 802 A boolean of whether there is an edge from start to end. 803 """ 804 return end_label in self[start_label] 805 806 807 808__pdoc__ = { 809 'AdjacencyMatrixGraph.add_vertex': False, 810 'AdjacencyMatrixGraph.delete_vertex': False, 811 'AdjacencyListGraph.add_directed_edge': False, 812}
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 __contains__(self, label: str) -> bool: 200 """ 201 Args: 202 label (str): The vertex label. 203 Returns: 204 A boolean indicating if the vertex is in the graph. 205 """ 206 return label in self.label_index 207 208 def print_graph(self): 209 """ 210 Print the contents of the graph. 211 """ 212 print(" |", end="") 213 for label in self.labels: 214 print(f"{label:^3}", end=" ") 215 print() 216 print("----" * (len(self.array) + 1)) 217 for r, row in enumerate(self.array): 218 label = self.labels[r] 219 print(f"{label:^3}|", end="") 220 for col in row: 221 b = " T " if col else " " 222 print(b, end=" ") 223 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.
208 def print_graph(self): 209 """ 210 Print the contents of the graph. 211 """ 212 print(" |", end="") 213 for label in self.labels: 214 print(f"{label:^3}", end=" ") 215 print() 216 print("----" * (len(self.array) + 1)) 217 for r, row in enumerate(self.array): 218 label = self.labels[r] 219 print(f"{label:^3}|", end="") 220 for col in row: 221 b = " T " if col else " " 222 print(b, end=" ") 223 print()
Print the contents of the graph.
225class AdjacencyMatrixWeightedGraph(AdjacencyMatrixGraph): 226 """ 227 A weighted adjacency matrix graph implementation 228 (allows either directed or undirected representation) 229 vertex labels are string types 230 """ 231 def __init__(self, labels): 232 """ 233 Args: 234 labels: list of labels for each vertex (string types) 235 """ 236 super().__init__(labels) 237 238 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 239 """ 240 Add an edge to the graph. 241 242 Args: 243 start_label (str): The starting vertex label. 244 end_label (str): The ending vertex label. 245 weight: The weight of the vertex. 246 directed: Whether the edge is directed. 247 """ 248 a = self.label_index[start_label] 249 b = self.label_index[end_label] 250 251 self.array[a][b] = weight 252 self.array[a][a] = 0 253 self.array[b][b] = 0 254 255 if not directed: 256 self.array[b][a] = weight 257 258 def print_graph(self): 259 """ 260 Print the contents of the graph. 261 """ 262 print(" |", end="") 263 for label in self.labels: 264 print(f"{label:>3}", end=" ") 265 print() 266 print("----" * (len(self.array) + 1)) 267 for r, row in enumerate(self.array): 268 label = self.labels[r] 269 print(f"{label:^3}|", end="") 270 for col in row: 271 w = f"{col:3}" if col is not None else " " 272 print(w, end=" ") 273 print() 274 275 def edges(self) -> list: 276 """ 277 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight). 278 """ 279 edges = [] 280 vertex_count = len(self.labels) 281 for i in range(vertex_count): 282 for j in range(vertex_count): 283 weight = self.array[i][j] 284 if weight and i != j: 285 edges.append((self.labels[i], self.labels[j], weight)) 286 287 return edges 288 289 def undirected_edges(self) -> list: 290 """ 291 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight). 292 """ 293 edges = [] 294 vertex_count = len(self.labels) 295 for i in range(vertex_count): 296 for j in range(vertex_count): 297 weight = self.array[i][j] 298 if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges: 299 edges.append((self.labels[i], self.labels[j], weight)) 300 301 return edges 302 303 def is_edge(self, start_label: str, end_label: str) -> bool: 304 """ 305 Return boolean if an edge exists. 306 307 308 Args: 309 start_label (str): Starting vertex label. 310 end_label (str): Ending vertex label. 311 312 Returns: 313 A boolean of whether there is an edge from start to end. 314 """ 315 return super().is_edge(start_label, end_label) 316 317 def weightx(self, start: str, end: str) -> bool: 318 """ 319 Return weight of an edge (is this used???) 320 Args: 321 start_label: starting vertex label 322 end_label: starting vertex label 323 324 Returns: 325 weight value of an edge from start to end 326 """ 327 return super().is_edge(start, end) 328 329 def __getitem__(self, vertex: str) -> dict: 330 """ 331 Args: 332 vertex: vertex label 333 Returns: 334 a dictionary of adjacent vertex labels and weights 335 """ 336 index = self.label_index[vertex] 337 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
231 def __init__(self, labels): 232 """ 233 Args: 234 labels: list of labels for each vertex (string types) 235 """ 236 super().__init__(labels)
Args: labels: list of labels for each vertex (string types)
238 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 239 """ 240 Add an edge to the graph. 241 242 Args: 243 start_label (str): The starting vertex label. 244 end_label (str): The ending vertex label. 245 weight: The weight of the vertex. 246 directed: Whether the edge is directed. 247 """ 248 a = self.label_index[start_label] 249 b = self.label_index[end_label] 250 251 self.array[a][b] = weight 252 self.array[a][a] = 0 253 self.array[b][b] = 0 254 255 if not directed: 256 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.
258 def print_graph(self): 259 """ 260 Print the contents of the graph. 261 """ 262 print(" |", end="") 263 for label in self.labels: 264 print(f"{label:>3}", end=" ") 265 print() 266 print("----" * (len(self.array) + 1)) 267 for r, row in enumerate(self.array): 268 label = self.labels[r] 269 print(f"{label:^3}|", end="") 270 for col in row: 271 w = f"{col:3}" if col is not None else " " 272 print(w, end=" ") 273 print()
Print the contents of the graph.
275 def edges(self) -> list: 276 """ 277 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight). 278 """ 279 edges = [] 280 vertex_count = len(self.labels) 281 for i in range(vertex_count): 282 for j in range(vertex_count): 283 weight = self.array[i][j] 284 if weight and i != j: 285 edges.append((self.labels[i], self.labels[j], weight)) 286 287 return edges
Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight).
289 def undirected_edges(self) -> list: 290 """ 291 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight). 292 """ 293 edges = [] 294 vertex_count = len(self.labels) 295 for i in range(vertex_count): 296 for j in range(vertex_count): 297 weight = self.array[i][j] 298 if weight and i != j and (self.labels[j], self.labels[i], weight) not in edges: 299 edges.append((self.labels[i], self.labels[j], weight)) 300 301 return edges
Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight).
303 def is_edge(self, start_label: str, end_label: str) -> bool: 304 """ 305 Return boolean if an edge exists. 306 307 308 Args: 309 start_label (str): Starting vertex label. 310 end_label (str): Ending vertex label. 311 312 Returns: 313 A boolean of whether there is an edge from start to end. 314 """ 315 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.
317 def weightx(self, start: str, end: str) -> bool: 318 """ 319 Return weight of an edge (is this used???) 320 Args: 321 start_label: starting vertex label 322 end_label: starting vertex label 323 324 Returns: 325 weight value of an edge from start to end 326 """ 327 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
339class AdjacencyListGraph: 340 """ 341 A unweighted adjacency list vertex implementation 342 (allows either directed or undirected representation) 343 vertex labels are string types 344 """ 345 def __init__(self): 346 #: hash table of vertices in graph 347 self._adjacents = {} 348 349 def add_edge(self, start_label: str, end_label: str, directed=False): 350 """ 351 Add an edge to the graph. 352 353 Args: 354 start_label (str): The label of the starting vertex. 355 end_label (str): The label of the ending vertex. 356 directed (bool): Whether the edge is directed. 357 """ 358 self.add_directed_edge(start_label, end_label) 359 if not directed: 360 self.add_directed_edge(end_label, start_label) 361 362 def add_directed_edge(self, start_label: str, end_label: str): 363 """ 364 Add a directed edge to the graph. 365 366 Args: 367 start_label (str): The label of the starting vertex. 368 end_label (str): The label of the ending vertex. 369 """ 370 if start_label not in self._adjacents: 371 self._adjacents[start_label] = [end_label] 372 else: 373 if end_label not in self._adjacents[start_label]: 374 self._adjacents[start_label].append(end_label) 375 if end_label not in self._adjacents: 376 self._adjacents[end_label] = [] 377 378 def delete_edge(self, start_label: str, end_label: str, directed=False): 379 """ 380 Delete an edge in the graph. 381 382 Args: 383 start_label (str): Starting vertex label. 384 end_label (str): Ending vertex label. 385 directed (bool): Whether the edge is directed. 386 Raises: 387 KeyError: If the edge does exist. 388 """ 389 if start_label not in self._adjacents: 390 raise KeyError(f"Vertex {start_label} does not exist") 391 if end_label not in self._adjacents[start_label]: 392 raise KeyError(f"Vertex {end_label} does not exist") 393 self._adjacents[start_label].remove(end_label) 394 if not directed: 395 if end_label not in self._adjacents: 396 raise KeyError(f"Vertex {end_label} does not exist") 397 if start_label not in self._adjacents[end_label]: 398 raise KeyError(f"Vertex {start_label} does not exist") 399 self._adjacents[end_label].remove(start_label) 400 401 def vertices(self) -> list: 402 """ 403 Return a list of vertex labels of the graph 404 """ 405 return list(self._adjacents.keys()) 406 407 def adjacents(self, vertex: str) -> list: 408 """ 409 Return a list of adjacents of a given vertex 410 411 Args: 412 vertex (str): The label of the vertex. 413 """ 414 return self._adjacents[vertex] 415 416 def dfs_traverse(self, start_label: str): 417 """ 418 Return a list of vertices through depth first traversal starting at a given vertex. 419 420 Args: 421 start_label (str): The starting vertex label. 422 Returns: 423 A list of vertex labels. 424 """ 425 return self._df_rec_traverse(start_label, set(), []) 426 427 def _df_rec_traverse(self, start_label: str, visited, dflist): 428 """ 429 Helper depth first traversal recursive function. 430 """ 431 visited.add(start_label) 432 dflist.append(start_label) 433 434 for v in self[start_label]: 435 if v not in visited: 436 self._df_rec_traverse(v, visited, dflist) 437 return dflist 438 439 def bfs_traverse(self, start_label: str): 440 """ 441 Return a list of vertices through breadth first traversal starting at a given vertex 442 Args: 443 start_label (str): The starting vertex label. 444 Returns: 445 A list of vertex labels. 446 """ 447 visited = set() 448 q = Queue() 449 bflist = [] 450 451 q.enqueue(start_label) 452 visited.add(start_label) 453 bflist.append(start_label) 454 455 while len(q) > 0: 456 current = q.dequeue() 457 458 for v in self[current]: 459 if v not in visited: 460 visited.add(v) 461 q.enqueue(v) 462 bflist.append(v) 463 return bflist 464 465 def dfs(self, start_label:str, end_label:str) -> str: 466 """ 467 Recursive depth first search. 468 469 Args: 470 start_label (str): The starting vertex label. 471 end_label (str): The vertex label to search for. 472 473 Returns: 474 A vertex in the graph if found, None otherwise. 475 """ 476 def dfs_rec(current: str, end: str, visited): 477 if current == end: 478 return current 479 480 visited.add(current) 481 482 for v in self.adjacents(current): 483 if v not in visited: 484 result = dfs_rec(v, end, visited) 485 if result is not None: 486 return result 487 488 return None 489 490 return dfs_rec(start_label, end_label, set()) 491 492 def bfs(self, start_label: str, end_label: str) -> str: 493 """ 494 Breadth first search. 495 496 Args: 497 start_label (str): The starting vertex label. 498 end_label (str): The vertex label to search for. 499 500 Returns: 501 A vertex in the graph if found, None otherwise. 502 """ 503 visited = set() 504 queue = Queue() 505 506 visited.add(start_label) 507 queue.enqueue(start_label) 508 509 while len(queue) > 0: 510 current = queue.dequeue() 511 512 if current == end_label: 513 return current 514 515 for v in self[current]: 516 if v not in visited: 517 visited.add(v) 518 queue.enqueue(v) 519 520 return None 521 522 def __getitem__(self, label: str): 523 """ 524 Args: 525 vertex: vertex label 526 Returns: 527 a list of adjacent vertex labels 528 """ 529 530 return self._adjacents[label] 531 532 def is_edge(self, start_label: str, end_label: str) -> bool: 533 """ 534 Return boolean if an edge exists 535 Args: 536 start_label (str): The starting vertex label. 537 end_label (str): The ending vertex label. 538 539 Returns: 540 A boolean of whether there is an edge from start to end 541 """ 542 return end_label in self[start_label] 543 544 def __len__(self): 545 """ 546 Return the number of nodes in the graph. 547 Returns: 548 int: The number of nodes in the graph. 549 """ 550 return len(self._adjacents) 551 552 def __contains__(self, label: str) -> bool: 553 """ 554 Args: 555 label (str): The vertex label. 556 Returns: 557 A boolean indicating if the vertex is in the graph. 558 """ 559 return label in self._adjacents 560 561 def edges(self) -> list: 562 """ 563 Return a list of edges in the graph. Each edge is represented by a tuple (start, end) 564 """ 565 edges = [] 566 for start in self._adjacents.keys(): 567 for end in self.adjacents(start): 568 if start != end: 569 edges.append((start, end)) 570 return edges 571 572 def undirected_edges(self) -> list: 573 """ 574 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end) 575 """ 576 edges = [] 577 for start in self._adjacents.keys(): 578 for end in self.adjacents(start): 579 if start != end and (end, start) not in edges: 580 edges.append((start, end)) 581 return edges
A unweighted adjacency list vertex implementation (allows either directed or undirected representation) vertex labels are string types
349 def add_edge(self, start_label: str, end_label: str, directed=False): 350 """ 351 Add an edge to the graph. 352 353 Args: 354 start_label (str): The label of the starting vertex. 355 end_label (str): The label of the ending vertex. 356 directed (bool): Whether the edge is directed. 357 """ 358 self.add_directed_edge(start_label, end_label) 359 if not directed: 360 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.
362 def add_directed_edge(self, start_label: str, end_label: str): 363 """ 364 Add a directed edge to the graph. 365 366 Args: 367 start_label (str): The label of the starting vertex. 368 end_label (str): The label of the ending vertex. 369 """ 370 if start_label not in self._adjacents: 371 self._adjacents[start_label] = [end_label] 372 else: 373 if end_label not in self._adjacents[start_label]: 374 self._adjacents[start_label].append(end_label) 375 if end_label not in self._adjacents: 376 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.
378 def delete_edge(self, start_label: str, end_label: str, directed=False): 379 """ 380 Delete an edge in the graph. 381 382 Args: 383 start_label (str): Starting vertex label. 384 end_label (str): Ending vertex label. 385 directed (bool): Whether the edge is directed. 386 Raises: 387 KeyError: If the edge does exist. 388 """ 389 if start_label not in self._adjacents: 390 raise KeyError(f"Vertex {start_label} does not exist") 391 if end_label not in self._adjacents[start_label]: 392 raise KeyError(f"Vertex {end_label} does not exist") 393 self._adjacents[start_label].remove(end_label) 394 if not directed: 395 if end_label not in self._adjacents: 396 raise KeyError(f"Vertex {end_label} does not exist") 397 if start_label not in self._adjacents[end_label]: 398 raise KeyError(f"Vertex {start_label} does not exist") 399 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.
401 def vertices(self) -> list: 402 """ 403 Return a list of vertex labels of the graph 404 """ 405 return list(self._adjacents.keys())
Return a list of vertex labels of the graph
407 def adjacents(self, vertex: str) -> list: 408 """ 409 Return a list of adjacents of a given vertex 410 411 Args: 412 vertex (str): The label of the vertex. 413 """ 414 return self._adjacents[vertex]
Return a list of adjacents of a given vertex
Args: vertex (str): The label of the vertex.
416 def dfs_traverse(self, start_label: str): 417 """ 418 Return a list of vertices through depth first traversal starting at a given vertex. 419 420 Args: 421 start_label (str): The starting vertex label. 422 Returns: 423 A list of vertex labels. 424 """ 425 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.
439 def bfs_traverse(self, start_label: str): 440 """ 441 Return a list of vertices through breadth first traversal starting at a given vertex 442 Args: 443 start_label (str): The starting vertex label. 444 Returns: 445 A list of vertex labels. 446 """ 447 visited = set() 448 q = Queue() 449 bflist = [] 450 451 q.enqueue(start_label) 452 visited.add(start_label) 453 bflist.append(start_label) 454 455 while len(q) > 0: 456 current = q.dequeue() 457 458 for v in self[current]: 459 if v not in visited: 460 visited.add(v) 461 q.enqueue(v) 462 bflist.append(v) 463 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.
465 def dfs(self, start_label:str, end_label:str) -> str: 466 """ 467 Recursive depth first search. 468 469 Args: 470 start_label (str): The starting vertex label. 471 end_label (str): The vertex label to search for. 472 473 Returns: 474 A vertex in the graph if found, None otherwise. 475 """ 476 def dfs_rec(current: str, end: str, visited): 477 if current == end: 478 return current 479 480 visited.add(current) 481 482 for v in self.adjacents(current): 483 if v not in visited: 484 result = dfs_rec(v, end, visited) 485 if result is not None: 486 return result 487 488 return None 489 490 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.
492 def bfs(self, start_label: str, end_label: str) -> str: 493 """ 494 Breadth first search. 495 496 Args: 497 start_label (str): The starting vertex label. 498 end_label (str): The vertex label to search for. 499 500 Returns: 501 A vertex in the graph if found, None otherwise. 502 """ 503 visited = set() 504 queue = Queue() 505 506 visited.add(start_label) 507 queue.enqueue(start_label) 508 509 while len(queue) > 0: 510 current = queue.dequeue() 511 512 if current == end_label: 513 return current 514 515 for v in self[current]: 516 if v not in visited: 517 visited.add(v) 518 queue.enqueue(v) 519 520 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.
532 def is_edge(self, start_label: str, end_label: str) -> bool: 533 """ 534 Return boolean if an edge exists 535 Args: 536 start_label (str): The starting vertex label. 537 end_label (str): The ending vertex label. 538 539 Returns: 540 A boolean of whether there is an edge from start to end 541 """ 542 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
561 def edges(self) -> list: 562 """ 563 Return a list of edges in the graph. Each edge is represented by a tuple (start, end) 564 """ 565 edges = [] 566 for start in self._adjacents.keys(): 567 for end in self.adjacents(start): 568 if start != end: 569 edges.append((start, end)) 570 return edges
Return a list of edges in the graph. Each edge is represented by a tuple (start, end)
572 def undirected_edges(self) -> list: 573 """ 574 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end) 575 """ 576 edges = [] 577 for start in self._adjacents.keys(): 578 for end in self.adjacents(start): 579 if start != end and (end, start) not in edges: 580 edges.append((start, end)) 581 return edges
Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end)
583class AdjacencyListWeightedGraph(AdjacencyListGraph): 584 """ 585 A weighted adjacency list vertex implementation in Python 586 (allows either directed or undirected representation) 587 """ 588 def __init__(self): 589 #: hash table of vertices in graph 590 self._adjacents = {} 591 592 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 593 """ 594 Add an edge to the graph. 595 596 Args: 597 start_label: The starting vertex label. 598 end_label: The ending vertex label. 599 weight: The edge weight. 600 directed: Whether the edge is directed. 601 """ 602 if start_label not in self._adjacents: 603 self._adjacents[start_label] = {} 604 605 self._adjacents[start_label][end_label] = weight 606 607 if not directed: 608 if end_label not in self._adjacents: 609 self._adjacents[end_label] = {} 610 self._adjacents[end_label][start_label] = weight 611 612 def delete_edge(self, start_label: str, end_label: str, directed=False): 613 """ 614 Delete an edge in the graph. 615 616 Args: 617 start_label (str): Starting vertex label. 618 end_label (str): Ending vertex label. 619 directed (bool): Whether the edge is directed. 620 621 Raises: 622 KeyError: If the edge does exist. 623 """ 624 if start_label not in self._adjacents: 625 raise KeyError(f"Vertex {start_label} does not exist") 626 if end_label not in self._adjacents[start_label]: 627 raise KeyError(f"Vertex {end_label} does not exist") 628 del self._adjacents[start_label][end_label] 629 if not directed: 630 if end_label not in self._adjacents: 631 raise KeyError(f"Vertex {end_label} does not exist") 632 if start_label not in self._adjacents[end_label]: 633 raise KeyError(f"Vertex {start_label} does not exist") 634 del self._adjacents[end_label][start_label] 635 636 def adjacents(self, vertex: str) -> list: 637 """ 638 Return a list of adjacents of a given vertex 639 Args: 640 vertex: starting vertex label 641 """ 642 return self._adjacents[vertex] 643 644 def dfs_traverse(self): 645 """ 646 Perform depth first traversal. 647 """ 648 return self._df_traverse_rec(self, set()) 649 650 def _df_traverse_rec(self, vertex, visited=None): 651 """ 652 helper depth first traversal recursive function 653 """ 654 visited.add(vertex) 655 656 for v in vertex.adjacents: 657 if v not in visited: 658 v._df_traverse_rec(v, visited) 659 660 def bfs_traverse(self): 661 """ 662 Perform breadth first traversal. 663 """ 664 start = self 665 visited = set() 666 queue = Queue() 667 668 queue.enqueue(start) 669 670 while len(queue) > 0: 671 current = queue.dequeue() 672 673 if current not in visited: 674 visited.add(current) 675 676 for v in current.adjacents: 677 queue.enqueue(v) 678 679 def dfs(self, start_label: str, end_label: str) -> str: 680 """ 681 Recursive depth first search. 682 683 Args: 684 start_label: The starting vertex label. 685 end_label: The vertex label to search for. 686 Returns: 687 vertex in the graph 688 none if not found. 689 """ 690 return self.dfs_rec(start_label, end_label, set()) 691 692 def dfs_rec(self, current, end_label, visited=None): 693 """ 694 Helper depth-first search recursive function. 695 696 Args: 697 current: Current vertex 698 end_label: Target vertex label 699 visited: Set of visited vertices 700 701 Returns: 702 vertex in the graph if found, None otherwise. 703 """ 704 if visited is None: 705 visited = set() 706 707 if current == end_label: 708 return current 709 710 visited.add(current) 711 712 for v in self.adjacents(current): 713 if v not in visited: 714 result = self.dfs_rec(v, end_label, visited) 715 if result is not None: 716 return result 717 718 return None 719 720 721 def bfs(self, start_label: str, end_label: str) -> str: 722 """ 723 Recursive breadth first search. 724 725 Args: 726 end: vertex to search for 727 728 Returns: 729 vertex in the graph 730 None if not found. 731 """ 732 visited = set() 733 queue = Queue() 734 735 visited.add(start_label) 736 queue.enqueue(start_label) 737 738 while not queue.is_empty(): 739 current = queue.dequeue() 740 741 if current == end_label: 742 return current 743 744 for v in self[current]: 745 if v not in visited: 746 visited.add(v) 747 queue.enqueue(v) 748 749 return None 750 751 def __getitem__(self, vertex: str) -> dict: 752 """ 753 Args: 754 vertex: vertex label 755 Returns: 756 Return a hashtable of adjacent vertices 757 """ 758 if vertex not in self._adjacents: 759 return {} 760 return self._adjacents[vertex] 761 762 def __len__(self): 763 """ 764 Return the number of nodes in the graph. 765 766 Returns: 767 int: The number of nodes in the graph. 768 """ 769 return len(self._adjacents) 770 771 def edges(self) -> list: 772 """ 773 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight) 774 """ 775 edges = [] 776 for start in self._adjacents.keys(): 777 for end in self.adjacents(start): 778 weight = self[start][end] 779 if start != end: 780 edges.append((start, end, weight)) 781 return edges 782 783 def undirected_edges(self) -> list: 784 """ 785 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight) 786 """ 787 edges = [] 788 for start in self._adjacents.keys(): 789 for end in self.adjacents(start): 790 weight = self[start][end] 791 if start != end and (end, start, weight) not in edges: 792 edges.append((start, end, weight)) 793 return edges 794 795 def is_edge(self, start_label: str, end_label: str) -> bool: 796 """ 797 Return boolean if an edge exists 798 Args: 799 start_label (str): starting vertex label 800 end_label (str): starting vertex label 801 802 Returns: 803 A boolean of whether there is an edge from start to end. 804 """ 805 return end_label in self[start_label]
A weighted adjacency list vertex implementation in Python (allows either directed or undirected representation)
592 def add_edge(self, start_label: str, end_label: str, weight, directed=False): 593 """ 594 Add an edge to the graph. 595 596 Args: 597 start_label: The starting vertex label. 598 end_label: The ending vertex label. 599 weight: The edge weight. 600 directed: Whether the edge is directed. 601 """ 602 if start_label not in self._adjacents: 603 self._adjacents[start_label] = {} 604 605 self._adjacents[start_label][end_label] = weight 606 607 if not directed: 608 if end_label not in self._adjacents: 609 self._adjacents[end_label] = {} 610 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.
612 def delete_edge(self, start_label: str, end_label: str, directed=False): 613 """ 614 Delete an edge in the graph. 615 616 Args: 617 start_label (str): Starting vertex label. 618 end_label (str): Ending vertex label. 619 directed (bool): Whether the edge is directed. 620 621 Raises: 622 KeyError: If the edge does exist. 623 """ 624 if start_label not in self._adjacents: 625 raise KeyError(f"Vertex {start_label} does not exist") 626 if end_label not in self._adjacents[start_label]: 627 raise KeyError(f"Vertex {end_label} does not exist") 628 del self._adjacents[start_label][end_label] 629 if not directed: 630 if end_label not in self._adjacents: 631 raise KeyError(f"Vertex {end_label} does not exist") 632 if start_label not in self._adjacents[end_label]: 633 raise KeyError(f"Vertex {start_label} does not exist") 634 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.
636 def adjacents(self, vertex: str) -> list: 637 """ 638 Return a list of adjacents of a given vertex 639 Args: 640 vertex: starting vertex label 641 """ 642 return self._adjacents[vertex]
Return a list of adjacents of a given vertex Args: vertex: starting vertex label
644 def dfs_traverse(self): 645 """ 646 Perform depth first traversal. 647 """ 648 return self._df_traverse_rec(self, set())
Perform depth first traversal.
660 def bfs_traverse(self): 661 """ 662 Perform breadth first traversal. 663 """ 664 start = self 665 visited = set() 666 queue = Queue() 667 668 queue.enqueue(start) 669 670 while len(queue) > 0: 671 current = queue.dequeue() 672 673 if current not in visited: 674 visited.add(current) 675 676 for v in current.adjacents: 677 queue.enqueue(v)
Perform breadth first traversal.
679 def dfs(self, start_label: str, end_label: str) -> str: 680 """ 681 Recursive depth first search. 682 683 Args: 684 start_label: The starting vertex label. 685 end_label: The vertex label to search for. 686 Returns: 687 vertex in the graph 688 none if not found. 689 """ 690 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.
692 def dfs_rec(self, current, end_label, visited=None): 693 """ 694 Helper depth-first search recursive function. 695 696 Args: 697 current: Current vertex 698 end_label: Target vertex label 699 visited: Set of visited vertices 700 701 Returns: 702 vertex in the graph if found, None otherwise. 703 """ 704 if visited is None: 705 visited = set() 706 707 if current == end_label: 708 return current 709 710 visited.add(current) 711 712 for v in self.adjacents(current): 713 if v not in visited: 714 result = self.dfs_rec(v, end_label, visited) 715 if result is not None: 716 return result 717 718 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.
721 def bfs(self, start_label: str, end_label: str) -> str: 722 """ 723 Recursive breadth first search. 724 725 Args: 726 end: vertex to search for 727 728 Returns: 729 vertex in the graph 730 None if not found. 731 """ 732 visited = set() 733 queue = Queue() 734 735 visited.add(start_label) 736 queue.enqueue(start_label) 737 738 while not queue.is_empty(): 739 current = queue.dequeue() 740 741 if current == end_label: 742 return current 743 744 for v in self[current]: 745 if v not in visited: 746 visited.add(v) 747 queue.enqueue(v) 748 749 return None
Recursive breadth first search.
Args: end: vertex to search for
Returns: vertex in the graph None if not found.
771 def edges(self) -> list: 772 """ 773 Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight) 774 """ 775 edges = [] 776 for start in self._adjacents.keys(): 777 for end in self.adjacents(start): 778 weight = self[start][end] 779 if start != end: 780 edges.append((start, end, weight)) 781 return edges
Return a list of edges in the graph. Each edge is represented by a tuple (start, end, weight)
783 def undirected_edges(self) -> list: 784 """ 785 Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight) 786 """ 787 edges = [] 788 for start in self._adjacents.keys(): 789 for end in self.adjacents(start): 790 weight = self[start][end] 791 if start != end and (end, start, weight) not in edges: 792 edges.append((start, end, weight)) 793 return edges
Return a list of undirected edges in the graph. Each edge is represented by a tuple (start, end, weight)
795 def is_edge(self, start_label: str, end_label: str) -> bool: 796 """ 797 Return boolean if an edge exists 798 Args: 799 start_label (str): starting vertex label 800 end_label (str): starting vertex label 801 802 Returns: 803 A boolean of whether there is an edge from start to end. 804 """ 805 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.