2015-06-17 06:33:24 -05:00
|
|
|
#
|
2017-07-07 08:50:06 -05:00
|
|
|
# Copyright (C) 2015-2017 FreeIPA Contributors see COPYING for license
|
2015-06-17 06:33:24 -05:00
|
|
|
#
|
2017-07-07 08:50:06 -05:00
|
|
|
from collections import deque
|
2015-06-17 06:33:24 -05:00
|
|
|
|
|
|
|
|
2016-06-03 05:45:01 -05:00
|
|
|
class Graph(object):
|
2015-06-17 06:33:24 -05:00
|
|
|
"""
|
|
|
|
Simple oriented graph structure
|
|
|
|
|
|
|
|
G = (V, E) where G is graph, V set of vertices and E list of edges.
|
|
|
|
E = (tail, head) where tail and head are vertices
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.vertices = set()
|
|
|
|
self.edges = []
|
|
|
|
self._adj = dict()
|
|
|
|
|
|
|
|
def add_vertex(self, vertex):
|
|
|
|
self.vertices.add(vertex)
|
|
|
|
self._adj[vertex] = []
|
|
|
|
|
|
|
|
def add_edge(self, tail, head):
|
|
|
|
if tail not in self.vertices:
|
|
|
|
raise ValueError("tail is not a vertex")
|
2017-07-07 08:50:37 -05:00
|
|
|
|
2015-06-17 06:33:24 -05:00
|
|
|
if head not in self.vertices:
|
|
|
|
raise ValueError("head is not a vertex")
|
2017-07-07 08:50:37 -05:00
|
|
|
|
2015-06-17 06:33:24 -05:00
|
|
|
self.edges.append((tail, head))
|
|
|
|
self._adj[tail].append(head)
|
|
|
|
|
|
|
|
def remove_edge(self, tail, head):
|
2015-06-26 11:09:19 -05:00
|
|
|
try:
|
|
|
|
self.edges.remove((tail, head))
|
|
|
|
except KeyError:
|
|
|
|
raise ValueError(
|
2017-07-07 08:50:37 -05:00
|
|
|
"graph does not contain edge: ({0}, {1})".format(tail, head)
|
|
|
|
)
|
2015-06-17 06:33:24 -05:00
|
|
|
self._adj[tail].remove(head)
|
|
|
|
|
|
|
|
def remove_vertex(self, vertex):
|
2015-06-26 11:09:19 -05:00
|
|
|
try:
|
|
|
|
self.vertices.remove(vertex)
|
|
|
|
except KeyError:
|
2017-07-07 08:50:37 -05:00
|
|
|
raise ValueError(
|
|
|
|
"graph does not contain vertex: {0}".format(vertex)
|
|
|
|
)
|
2015-06-17 06:33:24 -05:00
|
|
|
|
|
|
|
# delete _adjacencies
|
|
|
|
del self._adj[vertex]
|
2016-09-26 07:08:17 -05:00
|
|
|
for adj in self._adj.values():
|
|
|
|
adj[:] = [v for v in adj if v != vertex]
|
2015-06-17 06:33:24 -05:00
|
|
|
|
|
|
|
# delete edges
|
2017-07-07 08:51:01 -05:00
|
|
|
self.edges = [
|
2018-07-11 15:30:12 -05:00
|
|
|
e for e in self.edges if vertex not in (e[0], e[1])
|
2017-07-07 08:51:01 -05:00
|
|
|
]
|
2015-06-17 06:33:24 -05:00
|
|
|
|
|
|
|
def get_tails(self, head):
|
|
|
|
"""
|
|
|
|
Get list of vertices where a vertex is on the right side of an edge
|
|
|
|
"""
|
|
|
|
return [e[0] for e in self.edges if e[1] == head]
|
|
|
|
|
|
|
|
def get_heads(self, tail):
|
|
|
|
"""
|
|
|
|
Get list of vertices where a vertex is on the left side of an edge
|
|
|
|
"""
|
|
|
|
return [e[1] for e in self.edges if e[0] == tail]
|
|
|
|
|
|
|
|
def bfs(self, start=None):
|
|
|
|
"""
|
|
|
|
Breadth-first search traversal of the graph from `start` vertex.
|
|
|
|
Return a set of all visited vertices
|
|
|
|
"""
|
|
|
|
if not start:
|
2017-07-07 08:50:06 -05:00
|
|
|
start = next(iter(self.vertices))
|
2015-06-17 06:33:24 -05:00
|
|
|
visited = set()
|
2017-07-07 08:50:06 -05:00
|
|
|
queue = deque([start])
|
|
|
|
|
2015-06-17 06:33:24 -05:00
|
|
|
while queue:
|
2017-07-07 08:50:06 -05:00
|
|
|
vertex = queue.popleft()
|
2015-06-17 06:33:24 -05:00
|
|
|
if vertex not in visited:
|
|
|
|
visited.add(vertex)
|
|
|
|
queue.extend(set(self._adj.get(vertex, [])) - visited)
|
|
|
|
return visited
|