from typing import Any, Callable
|
|
|
class Graph:
|
"""
|
represents an undirected and unweighted Graph
|
"""
|
def __init__(self):
|
self._adjacent: dict[int, set] = {}
|
self._vertex_attribute: dict[int, dict] = {}
|
|
def add_vertex(self, vertex:int):
|
if vertex not in self._adjacent:
|
if not isinstance(vertex, int):
|
raise TypeError(f"Argument {vertex} is not a valid vertex")
|
self._adjacent[vertex] = set()
|
self._vertex_attribute[vertex] = {}
|
pass
|
|
def add_edge(self, u: int, v: int):
|
self.add_vertex(u)
|
self.add_vertex(v)
|
v_neighbor = self._adjacent[v]
|
if u not in v_neighbor:
|
v_neighbor.add(u)
|
u_neighbor = self._adjacent[u]
|
if v not in u_neighbor:
|
u_neighbor.add(v)
|
pass
|
|
def add_edges(self, u: int, adjacent: list[int]):
|
for v in adjacent:
|
self.add_edge(u, v)
|
pass
|
|
def merge_attribute(self, vertex: int, properties: dict):
|
"""
|
merge a dict of attribute to a vertex. Usage example:
|
```
|
g = Graph();
|
g.add_vertex(1);
|
g.mergeAttribute(1, {'color': 0});
|
```
|
:param vertex:
|
:param properties:
|
:return: self
|
"""
|
if vertex not in self._adjacent:
|
raise ValueError(f"Graph does not include vertex {vertex}")
|
old_attributes = self._vertex_attribute[vertex]
|
self._vertex_attribute[vertex] = old_attributes | properties
|
return self
|
pass
|
|
def get_attribute(self, vertex: int):
|
if vertex in self._adjacent:
|
return self._vertex_attribute[vertex]
|
else:
|
raise ValueError(f"Graph does not include vertex {vertex}")
|
pass
|
|
def vertices(self):
|
"""
|
returns the set of vertices in not defined order.
|
:return:
|
"""
|
return self._adjacent.keys()
|
|
def for_each_vertices(self, action: Callable[[int], Any]):
|
"""
|
iterates over all vertices in this graph in ascending order.
|
:param action:
|
:return:
|
"""
|
for v in sorted( self.vertices() ):
|
action(v)
|
|
def adjacent_of(self, vertex: int):
|
if vertex not in self._adjacent:
|
raise ValueError(f"Graph does not include vertex {vertex}")
|
return sorted( self._adjacent[vertex] )
|
|
def for_each_edges(self, action: Callable[[int, int], Any]):
|
"""
|
iterates over all edges of the graph, executes the action on each edge.
|
:param action: a binary function, its parameters are start and end vertices of the visited edges
|
"""
|
visited_edges: dict[int, set] = {}
|
for start in sorted( self.vertices() ):
|
for end in self.adjacent_of(start):
|
(first, second) = (start, end) if (start >= end) else (end, start)
|
if first in visited_edges:
|
#visited_adjacent = visited_edges.get(first)
|
visited_adjacent = visited_edges[first]
|
if second not in visited_adjacent:
|
visited_adjacent.add(second)
|
action(start, end)
|
else:
|
adjacent: set[int] = set()
|
visited_edges[first] = adjacent
|
adjacent.add(second)
|
action(start, end)
|
|
|
|
def __repr__(self):
|
text = ""
|
sorted_key = sorted(self._adjacent.keys())
|
last_key = sorted_key.pop()
|
def adjacent_fn(vertex): return "[" + ", ".join([str(v) for v in self._adjacent[vertex]]) + "]"
|
for key in sorted_key:
|
text += f"{key} → {adjacent_fn(key)}\n"
|
text += f"{last_key} → {adjacent_fn(last_key)}"
|
return text
|
|
def to_dot(self) -> str:
|
pass
|