graph.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. # Copyright 2024 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """
  15. Credit to Bernd Klein for this graph class.
  16. http://www.python-course.eu/graphs_python.php
  17. Modifications by kmisquitta:
  18. 1) Added support for pretty printing
  19. 2) Added function to return a vertex's neighbours
  20. 3) Added support for traversing to a vertex more than once in find_all_paths
  21. 4) Removed support for adding edges that are sets
  22. 5) Removed support for multiple edges between two vertices
  23. 6) Added support for traversing beyond the end vertex in find_all_paths
  24. 7) Removed many unneeded features
  25. """
  26. """ A Python Class
  27. A simple Python graph class, demonstrating the essential
  28. facts and functionalities of graphs.
  29. """
  30. import pprint
  31. def is_line_segment_in_path(path, vertex_1, vertex_2):
  32. for i in range(len(path) - 1):
  33. if path[i] == vertex_1 and path[i + 1] == vertex_2 \
  34. or path[i] == vertex_2 and path[i + 1] == vertex_1:
  35. return True
  36. return False
  37. class Graph(object):
  38. def __init__(self, graph_dict={}):
  39. """ initializes a graph object """
  40. self.__graph_dict = graph_dict
  41. def get_vertices(self):
  42. """ returns the vertices of a graph """
  43. return list(self.__graph_dict.keys())
  44. def get_edges(self):
  45. """ returns the edges of a graph """
  46. return self.__generate_edges()
  47. def get_neighbours(self, vertex):
  48. """ returns the neighbours of a vertex """
  49. return list(self.__graph_dict[vertex])
  50. def add_vertex(self, vertex):
  51. """ If the vertex "vertex" is not in
  52. self.__graph_dict, a key "vertex" with an empty
  53. list as a value is added to the dictionary.
  54. Otherwise nothing has to be done.
  55. """
  56. if vertex not in self.__graph_dict:
  57. self.__graph_dict[vertex] = []
  58. def add_edge(self, edge):
  59. """ assumes that edge is of type tuple or list """
  60. if len(edge) < 2:
  61. return
  62. vertex1 = edge[0]
  63. vertex2 = edge[1]
  64. if vertex1 in self.__graph_dict:
  65. if not (vertex2 in self.get_neighbours(vertex1)):
  66. self.__graph_dict[vertex1].append(vertex2)
  67. else:
  68. self.__graph_dict[vertex1] = [vertex2]
  69. def __generate_edges(self):
  70. """ A static method generating the edges of the
  71. graph "graph". Edges are represented as sets
  72. with one (a loop back to the vertex) or two
  73. vertices
  74. """
  75. edges = []
  76. for vertex in self.__graph_dict:
  77. for neighbour in self.__graph_dict[vertex]:
  78. if {vertex, neighbour} not in edges:
  79. edges.append({vertex, neighbour})
  80. return edges
  81. def __str__(self):
  82. res = "vertices: "
  83. for k in self.__graph_dict:
  84. res += str(k) + " "
  85. res += "\nedges: "
  86. for edge in self.__generate_edges():
  87. res += str(edge) + " "
  88. return res
  89. def find_all_paths(self, start_vertex, end_vertex, path=[]):
  90. """ Recursive function that finds all paths from the start vertex to the end vertex.
  91. Starts from the start vertex and traverses through vertices until the end vertex is reached.
  92. If there are untraversed edges when the end vertex is reached, will continue traversing
  93. to check for paths back to the end vertex (loops).
  94. There is no limit to how many times a vertex can be traversed.
  95. An edge may be traversed only once.
  96. """
  97. graph = self.__graph_dict
  98. paths = []
  99. path = path + [start_vertex]
  100. if start_vertex == end_vertex:
  101. # Check if additional traversals is possible
  102. neighbours = self.get_neighbours(end_vertex)
  103. no_possible_traversals = True
  104. for neighbour in neighbours:
  105. if not is_line_segment_in_path(path, end_vertex, neighbour):
  106. no_possible_traversals = False
  107. break
  108. if no_possible_traversals: # Base case
  109. return [path]
  110. else:
  111. paths.append(path) # Add current path, continue finding
  112. if start_vertex not in graph:
  113. return []
  114. for vertex in graph[start_vertex]:
  115. if not is_line_segment_in_path(path, vertex, start_vertex):
  116. extended_paths = self.find_all_paths(vertex,
  117. end_vertex,
  118. path)
  119. for p in extended_paths:
  120. paths.append(p)
  121. return paths
  122. def prettyprint(self):
  123. pprint.pprint(self.__graph_dict)