graph.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. from functools import singledispatch
  2. import enum
  3. import numpy as np
  4. import matplotlib.pyplot as plt
  5. from matplotlib.patches import FancyArrowPatch
  6. from verticle import verticle
  7. from graph_types import graph_type
  8. class Graph:
  9. '''
  10. Graph by matrix
  11. '''
  12. __verticles = []
  13. __edges = []
  14. matrix = []
  15. graph_type: enum.EnumMeta
  16. def __init__(self, verticles: dict, matrix: np.ndarray):
  17. self.graph_type = graph_type.SIMPLE
  18. if not np.all(matrix == matrix.T): self.graph_type |= graph_type.DIRECTED
  19. if matrix.max() > 1: self.graph_type |= graph_type.WEIGHTED
  20. self.verticles = verticles
  21. self.matrix = matrix
  22. @singledispatch
  23. def build(structs) -> "Graph":
  24. raise Exception("Конструктор не работает")
  25. @build.register
  26. @staticmethod
  27. def build_verticles(verticles: dict):
  28. matrix = []
  29. for vert in verticles:
  30. to = verticles[vert]
  31. matrix.append( [1 if x in to else 0 for x in verticles] )
  32. matrix = np.array(matrix)
  33. return Graph(verticles, matrix)
  34. @build.register
  35. @staticmethod
  36. def build_matrix(matrix: np.ndarray):
  37. return Graph([str(i) for i in range(matrix.shape[0])], matrix)
  38. @build.register
  39. @staticmethod
  40. def build_matrix(matrix: list):
  41. return Graph([str(i) for i in range(len(matrix))], np.array(matrix))
  42. def __eq__(self, __o: object) -> bool:
  43. return np.array_equal(self.matrix, __o.matrix)
  44. def __and__(self, __o: "Graph"):
  45. result = self.copy()
  46. result.matrix = (self.matrix+__o.matrix==2).astype(int)
  47. return result
  48. def __or__(self, __o: "Graph"):
  49. result = self.copy()
  50. result.matrix = (self.matrix+__o.matrix>0).astype(int)
  51. return result
  52. def __sub__(self, __o: "Graph"):
  53. result = self.copy()
  54. result.matrix = (self.matrix-__o.matrix>0).astype(int)
  55. return result
  56. def __xor__(self, __o: "Graph"):
  57. result = self.copy()
  58. result.matrix = (self.matrix!=__o.matrix).astype(int)
  59. return result
  60. def __str__(self):
  61. result = " "+"".join([str(node) for node in self.verticles])+"\n"
  62. for i,row in enumerate(self.matrix):
  63. result += str(self.verticles[i]) + " " + "".join(map(str,row))+"\n"
  64. return result
  65. def __invert__(self):
  66. result = self.copy()
  67. result.matrix = (self.matrix==0).astype(int)
  68. return result
  69. def set(self, indexes: list[int]):
  70. '''
  71. Меняет вершины по заданым индексам.
  72. '''
  73. if len(indexes) != len(self.verticles):
  74. raise IndexError(f"indexes must be len of {len(self.verticles)}")
  75. new_matrix = self.matrix[indexes]
  76. self.matrix = new_matrix[:, indexes]
  77. self.__verticles = [self.verticles[x] for x in indexes]
  78. def replace(self, fro:str|verticle, to:str|verticle):
  79. '''
  80. Меняет 2 переданные вершины графа местами.
  81. '''
  82. index_fro = self.verticles.index(str(fro))
  83. index_to = self.verticles.index(str(to))
  84. indexes = [index_to if i==index_fro else index_fro if i==index_to else i for i in range(len(self.verticles))]
  85. self.set(indexes)
  86. def copy(self):
  87. return Graph(self.dict)
  88. def plot(self, x=0, y=0):
  89. '''
  90. x,y - смещение построения графа, если координаты вершин не заданы
  91. '''
  92. fig, ax = plt.subplots()
  93. if not isinstance(self.verticles[0], verticle):
  94. print("Перепись " + "="*32)
  95. space = np.linspace(np.pi/2, 2*np.pi+np.pi/2, len(self.verticles)+1)[:-1]
  96. self.__verticles = [verticle(name, np.cos(sp)+x, np.sin(sp)+y) for name, sp in zip(self.verticles, space)]
  97. print([vert.name for vert in self.verticles])
  98. print([vert.x for vert in self.verticles])
  99. print([vert.y for vert in self.verticles])
  100. for i,row in enumerate(self.matrix):
  101. for j,elem in enumerate(row):
  102. if elem == 0: continue
  103. if graph_type.DIRECTED in self.graph_type:
  104. arrow = FancyArrowPatch((self.verticles[i].x, self.verticles[i].y),
  105. (self.verticles[j].x, self.verticles[j].y),
  106. connectionstyle="arc3,rad=.05", arrowstyle='->', mutation_scale=30)
  107. ax.add_patch(arrow)
  108. else:
  109. plt.plot([self.verticles[i].x, self.verticles[j].x], [self.verticles[i].y, self.verticles[j].y], c='black')
  110. if graph_type.WEIGHTED in self.graph_type:
  111. va = 'bottom' if j >= i else 'top'
  112. aligment = 'left' if j >= i else 'right'
  113. ax.annotate(str(elem), (.5, .5), xycoords=arrow, ha='center',
  114. va=va, horizontalalignment=aligment, size=20)
  115. x = []
  116. y = []
  117. for v in self.verticles:
  118. x.append(v.x)
  119. y.append(v.y)
  120. v.plot()
  121. plt.scatter(x,y, c='red', zorder=1)
  122. @property
  123. def dict(self):
  124. review = {}
  125. if isinstance(self.__verticles[0],verticle): n_vert = [verticle(vert.name, vert.x, vert.y) for vert in self.verticles]
  126. else: n_vert = self.__verticles
  127. for vert, row in zip(n_vert, self.matrix):
  128. if isinstance(vert,verticle):
  129. review[vert] = [n_vert[i] for i,sym in enumerate(row) if sym]
  130. else:
  131. review[vert] = ''.join([self.__verticles[i] for i,sym in enumerate(row) if sym])
  132. return review
  133. @property
  134. def verticles(self):
  135. return self.__verticles
  136. @verticles.setter
  137. def verticles(self, verts):
  138. self.__verticles = verts
  139. if __name__ == "__main__":
  140. # g = Graph({'a':'ad','b':'ad','c':'bc','d':'bc', 'e':'ac', 'f':'bd'})
  141. # h = Graph({'a':'bd','b':'bc','c':'bc','d':'ac'})
  142. # print("graph G1:",g,sep="\n")
  143. # print("graph H1:",h,sep="\n")
  144. # print("graph G1 and H1:",g&h,sep="\n")
  145. # print("graph G1 or H1:",g|h,sep="\n")
  146. # print("graph G1 / H1:",g-h,sep="\n")
  147. # print("graph G1 xor H1:",g^h,sep="\n")
  148. # print("Invert G1:",~g)
  149. # g.plot(1, 1)
  150. # h.plot(3.2, 1)
  151. g = Graph.build([[0, 10, 30, 50, 10],
  152. [0, 0, 0, 0, 0],
  153. [0, 0, 0, 0, 10],
  154. [0, 40, 20, 0, 0],
  155. [10, 0, 10, 30, 0]])
  156. print(repr(g.graph_type))
  157. g.plot()
  158. print(g)
  159. plt.show()