Module treeOps.graph
Expand source code
from enum import Enum
from collections import deque, defaultdict
from functools import partial
class node_status(Enum):
"""List of possible visit status of graph nodes (graph traversal)."""
UNVISITED = 0
VISITED = 1
VISITING = 2
class graph(object):
def __init__(self):
"""Initializes a graph with a nested defaultdict of graphNodes.
example: {'A': {'B': [2, 3], 'C': [5, 1]}}
"""
self.vertices = defaultdict(partial(defaultdict, list))
class graphNode(object):
def __init__(self, data=None, status=node_status.UNVISITED):
"""Initializes a graph node.
Attributes:
data (any type): the node value.
children (dict): a dictionary of the children (adjacent nodes).
status (node_status): the visit status of the node (graph traversal).
distance (float): the distance from a source node to this node.
previous (graphNode): predecessor of this node in an optimal path from
a predefined source in a shortest path algorithm.
"""
self.data = data
self.children = defaultdict(list)
self.status = status
self.distance = float('inf')
self.previous = None
# comparison operators
def __lt__(self, other):
return self.data < other.data
def __le__(self, other):
return self.data < other.data or self.data == other.data
# the following are optional
# def __ne__(self, other):
# return self.data != other.data
#
# def __gt__(self, other):
# return self.data > other.data
#
# def __ge__(self, other):
# return self.data > other.data or self.data == other.data
def __str__(self):
"""Prints the data assigned to this node, and list of its children with the weight of the
edges connecting them.
"""
res = "'{}': {}".format(self._getData(),
[[node._getData(), self._getWeight(node)] for node in self._getChildren()])
return res
def _getStatus(self):
"""Returns the current visit status of a node."""
return self.status
def _setStatus(self, status):
"""Sets the visit status of a node.
Args:
status (node_status): the new visit status.
Returns:
(graphNode) the node with its status updated with the given status.
"""
self.status = status
def _getData(self):
"""Returns the data assigned to this node."""
return self.data
def _setData(self, data):
"""Sets the data of this node to a given value.
Args:
data (node val data type): the new data to be assigned to this node.
Returns:
(graphNode) the node with updated data.
"""
self.data = data
def _addAdjNode(self, node, weight=0):
"""Adds an adjacent node to this node.
Args:
node (graphNode): the new adjacent node.
weight (int): the weight of the edge connecting this node to the new node.
Returns:
(graphNode) this node with its dictionary of children updated with a new node added.
"""
self.children[node].append(weight) if weight not in self.children[node] \
else self.children[node]
def _getChildren(self):
"""Returns the keys of the dict of children of this node.
Returns:
(dict_keys) keys of the dictionary of children of this node.
"""
return self.children.keys()
def _getDistance(self):
"""Returns the value of the distance instance attribute of this node."""
return self.distance
def _setDistance(self, distance):
"""Sets the instance attribute distance to a given value.
Args:
distance (float): the new distance.
Returns:
(graphNode) the node with its distance updated with the given value.
"""
self.distance = distance
def _getPrevious(self):
"""Returns the value of the previous instance attribute of this node."""
return self.previous
def _setPrevious(self, node):
"""Sets the instance attribute previous to a given node.
Args:
node (graphNode): the new previous.
Returns:
(graphNode) this node with its previous updated with the given node.
"""
self.previous = node
def _getWeight(self, adjNode):
"""Returns the weight of the edge between this node and the given node if they are adjacent,
otherwise returns None.
"""
if adjNode in self.children:
return self.children[adjNode]
else:
return None
def _isAdjacent(self, node):
"""Returns True of this node is adjacent to the give node, else False.
Returns:
(boolean) whether or not this node and the given node are adjacent.
"""
return node in self.children
def __str__(self):
"""Returns a string representing the vertices and edges of this graph."""
res = ''
for vertex in self._getVerticesDict():
res += vertex.__str__() + '\n'
return res
def reset(self):
"""Resets the attributes of all the nodes in a graph to their default values."""
for vx in self._getVerticesDict():
vx._setStatus(node_status.UNVISITED)
vx._setDistance(float('inf'))
vx._setPrevious(None)
def __contains__(self, VxData):
"""Checks whether or not the graph contains a given data.
Args:
VxData (node val data type): the data to be searched for.
Returns:
(boolean) True if VxData is found, otherwise False.
"""
data = [k._getData() for k in self._getVerticesDict().keys()]
return VxData in data
def _getVerticesDict(self):
"""(helper function) Returns a nested (default) dictionary of the vertices of the graph.
Returns:
(defaultdict) the default dictionary of vertices of the graph.
"""
return self.vertices
def _getEdges(self):
"""Returns a set of edges of the graph.
NOTE:
- If there is an edge between v1, v2 with weight w, both (v1, v2, w) and (v2, v1, w) are
included. This is necessary for Bellman-Ford shortest path algorithm and also make it general
for directed graphs.
Returns:
(set of tuples): set of edges represented as (v1, v2, w).
"""
vertices = self._getVerticesDict()
edges = set()
for vx, vxchildren in vertices.items():
for child, weight in vxchildren.items():
# uncomment the following line to have only unique edges
# tmp = sorted([vx, child])
tmp = [vx, child]
for i in range(len(weight)):
edges.add((tmp[0], tmp[1], weight[i]))
return edges
def _getVertex(self, VxData):
"""(helper function) Returns a graph node that holds the given data.
Args:
VxData (node val data type): the data to be searched for.
Returns:
(graphNode) the graph node from this graph that has data equal to the given data.
"""
if VxData not in self:
print("Vertex {} doesn't exist.".format(VxData))
return None
for vertex in self._getVerticesDict():
if vertex._getData() == VxData:
return vertex
def add_vertex(self, VxData):
"""Adds a vertex to the graph.
Args:
VxData (node val data type): the data to be added to the graph.
Returns:
(graph) this graph with a new node added to it as a new vertex.
"""
vx = self.graphNode(VxData)
if VxData in self._getVerticesDict():
print("Vertex {} already exists.".format(VxData))
else:
self.vertices[vx] = defaultdict(list)
def add_edge(self, fromVxData, toVxData, weight=0.0, directed=False):
"""Adds an edge to the graph given the data stored in the nodes at its ends.
Args:
fromVxData (node val data type): data of the "from" node.
toVxData (node val data type): data of the "to" node.
weight (float): weight of the edge.
directed (boolean): whether or not the edge is directed from fromVxData to toVxData.
Returns:
(graph) this graph updated with a new edge added to it.
"""
if fromVxData not in self:
print("Adding edge failed! Vertex {} doesn't exist.".format(fromVxData))
return
if toVxData not in self:
print("Adding edge failed! Vertex {} doesn't exist.".format(toVxData))
return
a = self._getVertex(fromVxData)
b = self._getVertex(toVxData)
edges = self._getEdges()
msg = "The edge ({}, {}, {}) already exists!"
msgcmpl = msg + " ({}, {}, {}) will be created."
if directed:
if (a, b, weight) in edges:
print(msg.format(fromVxData, toVxData, weight))
return
a._addAdjNode(b, weight)
self.vertices[a][b].append(weight)
elif not directed:
if (a, b, weight) in edges and (b, a, weight) not in edges:
print(msgcmpl.format(fromVxData, toVxData, weight, toVxData, fromVxData, weight))
b._addAdjNode(a, weight)
self.vertices[b][a].append(weight)
return
elif (b, a, weight) in edges and (a, b, weight) not in edges:
print(msgcmpl.format(toVxData, fromVxData, weight, fromVxData, toVxData, weight))
a._addAdjNode(b, weight)
self.vertices[a][b].append(weight)
return
elif (a, b, weight) in edges and (b, a, weight) in edges:
return
a._addAdjNode(b, weight)
b._addAdjNode(a, weight)
self.vertices[a][b].append(weight)
self.vertices[b][a].append(weight)
def _isMultigraph(self):
"""(helper function) Checks whether or not the graph is a multigraph."""
vertices = self._getVerticesDict()
for vx, vxchildren in vertices.items():
for weight in vxchildren.values():
if len(weight) > 1:
return True
return False
def DFS(self, start, path=None):
"""Depth-First Search (DFS).
Args:
start (node val data type): the key value of the node where the search starts.
path (list of graphNode): the DFS path (empty path to be filled with nodes).
Returns:
(list of graphNode) the full DFS path.
"""
if path is None:
path = []
s = self._getVertex(start)
if s is None:
return
if len(path) == 0: path.append(s)
s._setStatus(node_status.VISITED)
for child in s._getChildren():
if child._getStatus() == node_status.UNVISITED:
path.append(child)
path = self.DFS(child._getData(), path)
return path
def BFS(self, start):
"""Breadth-First Search (BFS).
Args:
start (node val data type): the key value of the node where the search starts.
Returns:
(list of graphNode) the full BFS path.
"""
s = self._getVertex(start)
if s is None:
return
s._setStatus(node_status.VISITED)
path = []
Q = deque()
Q.append(s)
while Q:
k = Q.popleft()
for child in k._getChildren():
if child._getStatus() == node_status.UNVISITED:
child._setStatus(node_status.VISITED)
Q.append(child)
k._setStatus(node_status.VISITED)
path.append(k)
return path
Classes
class graph
-
Initializes a graph with a nested defaultdict of graphNodes. example: {'A': {'B': [2, 3], 'C': [5, 1]}}
Expand source code
class graph(object): def __init__(self): """Initializes a graph with a nested defaultdict of graphNodes. example: {'A': {'B': [2, 3], 'C': [5, 1]}} """ self.vertices = defaultdict(partial(defaultdict, list)) class graphNode(object): def __init__(self, data=None, status=node_status.UNVISITED): """Initializes a graph node. Attributes: data (any type): the node value. children (dict): a dictionary of the children (adjacent nodes). status (node_status): the visit status of the node (graph traversal). distance (float): the distance from a source node to this node. previous (graphNode): predecessor of this node in an optimal path from a predefined source in a shortest path algorithm. """ self.data = data self.children = defaultdict(list) self.status = status self.distance = float('inf') self.previous = None # comparison operators def __lt__(self, other): return self.data < other.data def __le__(self, other): return self.data < other.data or self.data == other.data # the following are optional # def __ne__(self, other): # return self.data != other.data # # def __gt__(self, other): # return self.data > other.data # # def __ge__(self, other): # return self.data > other.data or self.data == other.data def __str__(self): """Prints the data assigned to this node, and list of its children with the weight of the edges connecting them. """ res = "'{}': {}".format(self._getData(), [[node._getData(), self._getWeight(node)] for node in self._getChildren()]) return res def _getStatus(self): """Returns the current visit status of a node.""" return self.status def _setStatus(self, status): """Sets the visit status of a node. Args: status (node_status): the new visit status. Returns: (graphNode) the node with its status updated with the given status. """ self.status = status def _getData(self): """Returns the data assigned to this node.""" return self.data def _setData(self, data): """Sets the data of this node to a given value. Args: data (node val data type): the new data to be assigned to this node. Returns: (graphNode) the node with updated data. """ self.data = data def _addAdjNode(self, node, weight=0): """Adds an adjacent node to this node. Args: node (graphNode): the new adjacent node. weight (int): the weight of the edge connecting this node to the new node. Returns: (graphNode) this node with its dictionary of children updated with a new node added. """ self.children[node].append(weight) if weight not in self.children[node] \ else self.children[node] def _getChildren(self): """Returns the keys of the dict of children of this node. Returns: (dict_keys) keys of the dictionary of children of this node. """ return self.children.keys() def _getDistance(self): """Returns the value of the distance instance attribute of this node.""" return self.distance def _setDistance(self, distance): """Sets the instance attribute distance to a given value. Args: distance (float): the new distance. Returns: (graphNode) the node with its distance updated with the given value. """ self.distance = distance def _getPrevious(self): """Returns the value of the previous instance attribute of this node.""" return self.previous def _setPrevious(self, node): """Sets the instance attribute previous to a given node. Args: node (graphNode): the new previous. Returns: (graphNode) this node with its previous updated with the given node. """ self.previous = node def _getWeight(self, adjNode): """Returns the weight of the edge between this node and the given node if they are adjacent, otherwise returns None. """ if adjNode in self.children: return self.children[adjNode] else: return None def _isAdjacent(self, node): """Returns True of this node is adjacent to the give node, else False. Returns: (boolean) whether or not this node and the given node are adjacent. """ return node in self.children def __str__(self): """Returns a string representing the vertices and edges of this graph.""" res = '' for vertex in self._getVerticesDict(): res += vertex.__str__() + '\n' return res def reset(self): """Resets the attributes of all the nodes in a graph to their default values.""" for vx in self._getVerticesDict(): vx._setStatus(node_status.UNVISITED) vx._setDistance(float('inf')) vx._setPrevious(None) def __contains__(self, VxData): """Checks whether or not the graph contains a given data. Args: VxData (node val data type): the data to be searched for. Returns: (boolean) True if VxData is found, otherwise False. """ data = [k._getData() for k in self._getVerticesDict().keys()] return VxData in data def _getVerticesDict(self): """(helper function) Returns a nested (default) dictionary of the vertices of the graph. Returns: (defaultdict) the default dictionary of vertices of the graph. """ return self.vertices def _getEdges(self): """Returns a set of edges of the graph. NOTE: - If there is an edge between v1, v2 with weight w, both (v1, v2, w) and (v2, v1, w) are included. This is necessary for Bellman-Ford shortest path algorithm and also make it general for directed graphs. Returns: (set of tuples): set of edges represented as (v1, v2, w). """ vertices = self._getVerticesDict() edges = set() for vx, vxchildren in vertices.items(): for child, weight in vxchildren.items(): # uncomment the following line to have only unique edges # tmp = sorted([vx, child]) tmp = [vx, child] for i in range(len(weight)): edges.add((tmp[0], tmp[1], weight[i])) return edges def _getVertex(self, VxData): """(helper function) Returns a graph node that holds the given data. Args: VxData (node val data type): the data to be searched for. Returns: (graphNode) the graph node from this graph that has data equal to the given data. """ if VxData not in self: print("Vertex {} doesn't exist.".format(VxData)) return None for vertex in self._getVerticesDict(): if vertex._getData() == VxData: return vertex def add_vertex(self, VxData): """Adds a vertex to the graph. Args: VxData (node val data type): the data to be added to the graph. Returns: (graph) this graph with a new node added to it as a new vertex. """ vx = self.graphNode(VxData) if VxData in self._getVerticesDict(): print("Vertex {} already exists.".format(VxData)) else: self.vertices[vx] = defaultdict(list) def add_edge(self, fromVxData, toVxData, weight=0.0, directed=False): """Adds an edge to the graph given the data stored in the nodes at its ends. Args: fromVxData (node val data type): data of the "from" node. toVxData (node val data type): data of the "to" node. weight (float): weight of the edge. directed (boolean): whether or not the edge is directed from fromVxData to toVxData. Returns: (graph) this graph updated with a new edge added to it. """ if fromVxData not in self: print("Adding edge failed! Vertex {} doesn't exist.".format(fromVxData)) return if toVxData not in self: print("Adding edge failed! Vertex {} doesn't exist.".format(toVxData)) return a = self._getVertex(fromVxData) b = self._getVertex(toVxData) edges = self._getEdges() msg = "The edge ({}, {}, {}) already exists!" msgcmpl = msg + " ({}, {}, {}) will be created." if directed: if (a, b, weight) in edges: print(msg.format(fromVxData, toVxData, weight)) return a._addAdjNode(b, weight) self.vertices[a][b].append(weight) elif not directed: if (a, b, weight) in edges and (b, a, weight) not in edges: print(msgcmpl.format(fromVxData, toVxData, weight, toVxData, fromVxData, weight)) b._addAdjNode(a, weight) self.vertices[b][a].append(weight) return elif (b, a, weight) in edges and (a, b, weight) not in edges: print(msgcmpl.format(toVxData, fromVxData, weight, fromVxData, toVxData, weight)) a._addAdjNode(b, weight) self.vertices[a][b].append(weight) return elif (a, b, weight) in edges and (b, a, weight) in edges: return a._addAdjNode(b, weight) b._addAdjNode(a, weight) self.vertices[a][b].append(weight) self.vertices[b][a].append(weight) def _isMultigraph(self): """(helper function) Checks whether or not the graph is a multigraph.""" vertices = self._getVerticesDict() for vx, vxchildren in vertices.items(): for weight in vxchildren.values(): if len(weight) > 1: return True return False def DFS(self, start, path=None): """Depth-First Search (DFS). Args: start (node val data type): the key value of the node where the search starts. path (list of graphNode): the DFS path (empty path to be filled with nodes). Returns: (list of graphNode) the full DFS path. """ if path is None: path = [] s = self._getVertex(start) if s is None: return if len(path) == 0: path.append(s) s._setStatus(node_status.VISITED) for child in s._getChildren(): if child._getStatus() == node_status.UNVISITED: path.append(child) path = self.DFS(child._getData(), path) return path def BFS(self, start): """Breadth-First Search (BFS). Args: start (node val data type): the key value of the node where the search starts. Returns: (list of graphNode) the full BFS path. """ s = self._getVertex(start) if s is None: return s._setStatus(node_status.VISITED) path = [] Q = deque() Q.append(s) while Q: k = Q.popleft() for child in k._getChildren(): if child._getStatus() == node_status.UNVISITED: child._setStatus(node_status.VISITED) Q.append(child) k._setStatus(node_status.VISITED) path.append(k) return path
Class variables
var graphNode
Methods
def BFS(self, start)
-
Breadth-First Search (BFS).
Args
start
:node val data type
- the key value of the node where the search starts.
Returns
(list of graphNode) the full BFS path.
Expand source code
def BFS(self, start): """Breadth-First Search (BFS). Args: start (node val data type): the key value of the node where the search starts. Returns: (list of graphNode) the full BFS path. """ s = self._getVertex(start) if s is None: return s._setStatus(node_status.VISITED) path = [] Q = deque() Q.append(s) while Q: k = Q.popleft() for child in k._getChildren(): if child._getStatus() == node_status.UNVISITED: child._setStatus(node_status.VISITED) Q.append(child) k._setStatus(node_status.VISITED) path.append(k) return path
def DFS(self, start, path=None)
-
Depth-First Search (DFS).
Args
start
:node val data type
- the key value of the node where the search starts.
path
:list
ofgraphNode
- the DFS path (empty path to be filled with nodes).
Returns
(list of graphNode) the full DFS path.
Expand source code
def DFS(self, start, path=None): """Depth-First Search (DFS). Args: start (node val data type): the key value of the node where the search starts. path (list of graphNode): the DFS path (empty path to be filled with nodes). Returns: (list of graphNode) the full DFS path. """ if path is None: path = [] s = self._getVertex(start) if s is None: return if len(path) == 0: path.append(s) s._setStatus(node_status.VISITED) for child in s._getChildren(): if child._getStatus() == node_status.UNVISITED: path.append(child) path = self.DFS(child._getData(), path) return path
def add_edge(self, fromVxData, toVxData, weight=0.0, directed=False)
-
Adds an edge to the graph given the data stored in the nodes at its ends.
Args
fromVxData
:node val data type
- data of the "from" node.
toVxData
:node val data type
- data of the "to" node.
weight
:float
- weight of the edge.
directed
:boolean
- whether or not the edge is directed from fromVxData to toVxData.
Returns
(graph) this graph updated with a new edge added to it.
Expand source code
def add_edge(self, fromVxData, toVxData, weight=0.0, directed=False): """Adds an edge to the graph given the data stored in the nodes at its ends. Args: fromVxData (node val data type): data of the "from" node. toVxData (node val data type): data of the "to" node. weight (float): weight of the edge. directed (boolean): whether or not the edge is directed from fromVxData to toVxData. Returns: (graph) this graph updated with a new edge added to it. """ if fromVxData not in self: print("Adding edge failed! Vertex {} doesn't exist.".format(fromVxData)) return if toVxData not in self: print("Adding edge failed! Vertex {} doesn't exist.".format(toVxData)) return a = self._getVertex(fromVxData) b = self._getVertex(toVxData) edges = self._getEdges() msg = "The edge ({}, {}, {}) already exists!" msgcmpl = msg + " ({}, {}, {}) will be created." if directed: if (a, b, weight) in edges: print(msg.format(fromVxData, toVxData, weight)) return a._addAdjNode(b, weight) self.vertices[a][b].append(weight) elif not directed: if (a, b, weight) in edges and (b, a, weight) not in edges: print(msgcmpl.format(fromVxData, toVxData, weight, toVxData, fromVxData, weight)) b._addAdjNode(a, weight) self.vertices[b][a].append(weight) return elif (b, a, weight) in edges and (a, b, weight) not in edges: print(msgcmpl.format(toVxData, fromVxData, weight, fromVxData, toVxData, weight)) a._addAdjNode(b, weight) self.vertices[a][b].append(weight) return elif (a, b, weight) in edges and (b, a, weight) in edges: return a._addAdjNode(b, weight) b._addAdjNode(a, weight) self.vertices[a][b].append(weight) self.vertices[b][a].append(weight)
def add_vertex(self, VxData)
-
Adds a vertex to the graph.
Args
VxData
:node val data type
- the data to be added to the graph.
Returns
(graph) this graph with a new node added to it as a new vertex.
Expand source code
def add_vertex(self, VxData): """Adds a vertex to the graph. Args: VxData (node val data type): the data to be added to the graph. Returns: (graph) this graph with a new node added to it as a new vertex. """ vx = self.graphNode(VxData) if VxData in self._getVerticesDict(): print("Vertex {} already exists.".format(VxData)) else: self.vertices[vx] = defaultdict(list)
def reset(self)
-
Resets the attributes of all the nodes in a graph to their default values.
Expand source code
def reset(self): """Resets the attributes of all the nodes in a graph to their default values.""" for vx in self._getVerticesDict(): vx._setStatus(node_status.UNVISITED) vx._setDistance(float('inf')) vx._setPrevious(None)
class node_status (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
List of possible visit status of graph nodes (graph traversal).
Expand source code
class node_status(Enum): """List of possible visit status of graph nodes (graph traversal).""" UNVISITED = 0 VISITED = 1 VISITING = 2
Ancestors
- enum.Enum
Class variables
var UNVISITED
var VISITED
var VISITING