-
Notifications
You must be signed in to change notification settings - Fork 0
/
_graph.py
257 lines (223 loc) · 9.29 KB
/
_graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
"""Implementation der Klasse `Graph`."""
from __future__ import annotations
__all__: Final[list[str]] = ["Graph"]
from io import StringIO
from typing import TYPE_CHECKING, Final
from nrw.datastructures._list import List
if TYPE_CHECKING:
from nrw.datastructures._edge import Edge
from nrw.datastructures._vertex import Vertex
class Graph:
"""Die Klasse `Graph` stellt einen ungerichteten, kantengewichteten Graphen dar.
Es können Knoten- und Kantenobjekte hinzugefügt und entfernt,
flache Kopien der Knoten- und Kantenlisten des Graphen angefragt
und Markierungen von Knoten und Kanten gesetzt und überprueft werden.
Des Weiteren kann eine Liste der Nachbarn eines bestimmten Knoten,
eine Liste der inzidenten Kanten eines bestimmten Knoten
und die Kante von einem bestimmten Knoten zu einem
anderen bestimmten Knoten angefragt werden.
Abgesehen davon kann abgefragt werden, welches
Knotenobjekt zu einer bestimmten ID gehört und ob der Graph leer ist.
"""
__slots__: Final[tuple[str, str]] = ("_edges", "_vertices")
__hash__ = None # type: ignore[assignment]
def __init__(self) -> None:
"""Ein Objekt vom Typ `Graph` wird erstellt.
Der von diesem Objekt repraesentierte Graph ist leer.
"""
self._vertices: List[Vertex] = List()
self._edges: List[Edge] = List()
def __repr__(self) -> str:
return (
f"{self.__class__.__name__}(vertices={self._vertices!r}, "
f"edges={self._edges!r})"
)
def __str__(self) -> str:
if self.is_empty:
return f"{self.__class__.__name__}()"
with StringIO() as buffer:
buffer.write(f"{self.__class__.__name__}(")
self._vertices.to_first()
while self._vertices.has_access:
current: Vertex | None = self._vertices.content
assert current is not None
buffer.write(f"{current} -> {self.get_neighbours(current)}, ")
self._vertices.next()
return f"{buffer.getvalue().rstrip(', ')})"
@property
def vertices(self) -> List[Vertex]:
"""Die Anfrage liefert eine neue Liste aller Knotenobjekte
vom Typ `List[Vertex]`.
"""
result: List[Vertex] = List()
self._vertices.to_first()
while self._vertices.has_access:
result.append(self._vertices.content)
self._vertices.next()
return result
@property
def edges(self) -> List[Edge]:
"""Die Anfrage liefert eine neue Liste aller Kantenobjekte
vom Typ `List[Edge]`.
"""
result: List[Edge] = List()
self._edges.to_first()
while self._edges.has_access:
result.append(self._edges.content)
self._edges.next()
return result
def get_vertex(self, id_: str) -> Vertex | None:
"""Die Anfrage liefert das Knotenobjekt mit `id_` als ID.
Ist ein solchen Knotenobjekt nicht im Graphen enthalten,
wird `None` zurückgeliefert.
"""
self._vertices.to_first()
while self._vertices.has_access:
if self._vertices.content.id == id_:
return self._vertices.content
self._vertices.next()
return None
def add_vertex(self, vertex: Vertex | None) -> None:
"""Der Auftrag fügt den Knoten `vertex` in den Graphen ein,
sofern es noch keinen Knoten mit demselben ID-Eintrag
wie `vertex` im Graphen gibt und `vertex` eine ID hat, welche nicht `None` ist.
Ansonsten passiert nichts.
"""
if vertex is None or vertex.id is None:
return
self._vertices.to_first()
while self._vertices.has_access:
if self._vertices.content.id == vertex.id:
return
self._vertices.next()
self._vertices.append(vertex)
def remove_vertex(self, vertex: Vertex) -> None:
"""Der Auftrag entfernt den Knoten `vertex` aus dem Graphen
und löscht alle Kanten, die mit ihm inzident sind.
Ist der Knoten `vertex` nicht im Graphen enthalten, passiert nichts.
"""
self._edges.to_first()
while self._edges.has_access:
if vertex in self._edges.content.vertices:
self._edges.remove()
else:
self._edges.next()
self._vertices.to_first()
while self._vertices.has_access and self._vertices.content is not vertex:
self._vertices.next()
if self._vertices.has_access:
self._vertices.remove()
def get_edge(self, vertex: Vertex, another_vertex: Vertex) -> Edge | None:
"""Die Anfrage liefert die Kante, welche die Knoten `vertex`
und `another_vertex` verbindet, als Objekt vom Typ `Edge`.
Ist der Knoten `vertex` oder der Knoten `another_vertex` nicht
im Graphen enthalten oder gibt es keine Kante, die beide Knoten verbindet,
so wird `None` zurückgeliefert.
"""
self._edges.to_first()
while self._edges.has_access:
vertex1, vertex2 = self._edges.content.vertices
if (vertex1 is vertex and vertex2 is another_vertex) or (
vertex1 is another_vertex and vertex2 is vertex
):
return self._edges.content
self._edges.next()
return None
def add_edge(self, edge: Edge | None) -> None:
"""Der Auftrag fügt die Kante `edge` in den Graphen ein,
sofern beide durch die Kante verbundenen Knoten im Graphen enthalten sind,
nicht identisch sind und noch keine Kante zwischen den Knoten existiert.
Ansonsten passiert nichts.
"""
if edge is None:
return
vertex1, vertex2 = edge.vertices
# pylint: disable=R0916
if (
vertex1 is not None
and vertex2 is not None
and self.get_vertex(vertex1.id) is vertex1
and self.get_vertex(vertex2.id) is vertex2
and self.get_edge(vertex1, vertex2) is None
and vertex1 is not vertex2
):
self._edges.append(edge)
def remove_edge(self, edge: Edge) -> None:
"""Der Auftrag entfernt die Kante `edge` aus dem Graphen.
Ist die Kante `edge` nicht im Graphen enthalten, passiert nichts.
"""
self._edges.to_first()
while self._edges.has_access:
if self._edges.content is edge:
self._edges.remove()
return
self._edges.next()
def set_all_vertex_marks(self, mark: bool) -> None:
"""Der Auftrag setzt die Markierungen aller Knoten des Graphen auf `mark`."""
self._vertices.to_first()
while self._vertices.has_access:
self._vertices.content.mark = mark
self._vertices.next()
def all_vertices_marked(self) -> bool:
"""Die Anfrage liefert `True`,
wenn alle Knoten des Graphen mit `True` markiert sind, ansonsten `False`.
"""
self._vertices.to_first()
while self._vertices.has_access:
if not self._vertices.content.is_marked:
return False
self._vertices.next()
return True
def set_all_edge_marks(self, mark: bool) -> None:
"""Der Auftrag setzt die Markierungen aller Kanten des Graphen auf `mark`."""
self._edges.to_first()
while self._edges.has_access:
self._edges.content.mark = mark
self._edges.next()
def all_edges_marked(self) -> bool:
"""Die Anfrage liefert `True`,
wenn alle Kanten des Graphen mit `True` markiert sind, ansonsten `False`.
"""
self._edges.to_first()
while self._edges.has_access:
if not self._edges.content.mark:
return False
self._edges.next()
return True
def get_neighbours(self, vertex: Vertex) -> List[Vertex]:
"""Die Anfrage liefert alle Nachbarn des Knotens `vertex`
als neue Liste vom Typ `List[Vertex]`.
Hat der Knoten `vertex` keine Nachbarn in diesem Graphen
oder ist gar nicht in diesem Graphen enthalten,
so wird eine leere Liste zurückgeliefert.
"""
result: List[Vertex] = List()
self._edges.to_first()
while self._edges.has_access:
vertex1, vertex2 = self._edges.content.vertices
if vertex is vertex1:
result.append(vertex2)
elif vertex is vertex2:
result.append(vertex1)
self._edges.next()
return result
def get_edges(self, vertex: Vertex) -> List[Edge]:
"""Die Anfrage liefert eine neue Liste alle inzidenten Kanten
zum Knoten `vertex`.
Hat der Knoten `vertex` keine inzidenten Kanten in diesem Graphen
oder ist gar nicht in diesem Graphen enthalten,
so wird eine leere Liste zurückgeliefert.
"""
result: List[Edge] = List()
self._edges.to_first()
while self._edges.has_access:
if vertex in self._edges.content.vertices:
result.append(self._edges.content)
self._edges.next()
return result
@property
def is_empty(self) -> bool:
"""Die Anfrage liefert `True`, wenn der Graph keine Knoten enthält,
ansonsten `False`.
"""
return self._vertices.is_empty