511 lines
12 KiB
Python
511 lines
12 KiB
Python
|
#!/usr/bin/env python
|
||
|
# -*- coding: utf-8 -*-
|
||
|
#
|
||
|
# Licensed under the GNU LGPL v2.1 - http://www.gnu.org/licenses/lgpl.html
|
||
|
|
||
|
"""This module contains abstract class IGraph represents graphs interface and
|
||
|
class Graph (based on IGraph) which implements undirected graph.
|
||
|
|
||
|
Examples
|
||
|
--------
|
||
|
|
||
|
Create simple graph with 4 nodes.
|
||
|
|
||
|
>>> g = Graph()
|
||
|
>>> g.add_node('Felidae')
|
||
|
>>> g.add_node('Lion')
|
||
|
>>> g.add_node('Tiger')
|
||
|
>>> g.add_node('Wolf')
|
||
|
>>> sorted(g.nodes())
|
||
|
['Felidae', 'Lion', 'Tiger', 'Wolf']
|
||
|
|
||
|
Add some edges and check neighbours.
|
||
|
|
||
|
>>> g.add_edge(("Felidae", "Lion"))
|
||
|
>>> g.add_edge(("Felidae", "Tiger"))
|
||
|
>>> g.neighbors("Felidae")
|
||
|
['Lion', 'Tiger']
|
||
|
|
||
|
One node has no neighbours.
|
||
|
|
||
|
>>> g.neighbors("Wolf")
|
||
|
[]
|
||
|
|
||
|
"""
|
||
|
|
||
|
from abc import ABCMeta, abstractmethod
|
||
|
|
||
|
|
||
|
class IGraph(object):
|
||
|
"""Represents the interface or contract that the graph for TextRank
|
||
|
should implement.
|
||
|
"""
|
||
|
__metaclass__ = ABCMeta
|
||
|
|
||
|
@abstractmethod
|
||
|
def nodes(self):
|
||
|
"""Returns all nodes of graph.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
list of hashable
|
||
|
Nodes of graph.
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
@abstractmethod
|
||
|
def edges(self):
|
||
|
"""Returns all edges of graph.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
list of (hashable, hashable)
|
||
|
Edges of graph.
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
@abstractmethod
|
||
|
def neighbors(self, node):
|
||
|
"""Return all nodes that are directly accessible from given node.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
node : hashable
|
||
|
Given node identifier.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
list of hashable
|
||
|
Nodes directly accessible from given `node`.
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
@abstractmethod
|
||
|
def has_node(self, node):
|
||
|
"""Returns whether the requested node exists.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
node : hashable
|
||
|
Given node identifier.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
bool
|
||
|
True if `node` exists, False otherwise.
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
@abstractmethod
|
||
|
def add_node(self, node, attrs=None):
|
||
|
"""Adds given node to the graph.
|
||
|
|
||
|
Note
|
||
|
----
|
||
|
While nodes can be of any type, it's strongly recommended to use only numbers and single-line strings
|
||
|
as node identifiers if you intend to use write().
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
node : hashable
|
||
|
Given node
|
||
|
attrs : list, optional
|
||
|
Node attributes specified as (attribute, value)
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
@abstractmethod
|
||
|
def add_edge(self, edge, wt=1, label='', attrs=None):
|
||
|
"""Adds an edge to the graph connecting two nodes. An edge, here,
|
||
|
is a tuple of two nodes.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
wt : float, optional
|
||
|
Weight of new edge.
|
||
|
label : str, optional
|
||
|
Edge label.
|
||
|
attrs : list, optional
|
||
|
Node attributes specified as (attribute, value)
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
@abstractmethod
|
||
|
def has_edge(self, edge):
|
||
|
"""Returns whether an edge exists.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
bool
|
||
|
True if `edge` exists, False otherwise.
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
@abstractmethod
|
||
|
def edge_weight(self, edge):
|
||
|
"""Returns weigth of given edge.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
float
|
||
|
Edge weight.
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
@abstractmethod
|
||
|
def del_node(self, node):
|
||
|
"""Removes node and its edges from the graph.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
node : hashable
|
||
|
Node to delete.
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class Graph(IGraph):
|
||
|
"""
|
||
|
Implementation of an undirected graph, based on IGraph.
|
||
|
|
||
|
Attributes
|
||
|
----------
|
||
|
Graph.WEIGHT_ATTRIBUTE_NAME : str
|
||
|
Name of weight attribute in graph.
|
||
|
Graph.DEFAULT_WEIGHT : float
|
||
|
Weight set by default.
|
||
|
Graph.LABEL_ATTRIBUTE_NAME : str
|
||
|
Default name of attribute. Not used.
|
||
|
Graph.DEFAULT_LABEL : str
|
||
|
Label set by default. Not used.
|
||
|
|
||
|
"""
|
||
|
|
||
|
WEIGHT_ATTRIBUTE_NAME = "weight"
|
||
|
DEFAULT_WEIGHT = 0
|
||
|
|
||
|
LABEL_ATTRIBUTE_NAME = "label"
|
||
|
DEFAULT_LABEL = ""
|
||
|
|
||
|
def __init__(self):
|
||
|
"""Initializes object."""
|
||
|
|
||
|
# Metadata about edges
|
||
|
# Mapping: Edge -> Dict mapping, lablel-> str, wt->num
|
||
|
self.edge_properties = {}
|
||
|
# Key value pairs: (Edge -> Attributes)
|
||
|
self.edge_attr = {}
|
||
|
|
||
|
# Metadata about nodes
|
||
|
# Pairing: Node -> Attributes
|
||
|
self.node_attr = {}
|
||
|
# Pairing: Node -> Neighbors
|
||
|
self.node_neighbors = {}
|
||
|
|
||
|
def has_edge(self, edge):
|
||
|
"""Returns whether an edge exists.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
bool
|
||
|
True if `edge` exists, False otherwise.
|
||
|
|
||
|
"""
|
||
|
u, v = edge
|
||
|
return (u, v) in self.edge_properties and (v, u) in self.edge_properties
|
||
|
|
||
|
def edge_weight(self, edge):
|
||
|
"""Returns weight of given edge.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
float
|
||
|
Edge weight.
|
||
|
|
||
|
"""
|
||
|
return self.get_edge_properties(edge).setdefault(self.WEIGHT_ATTRIBUTE_NAME, self.DEFAULT_WEIGHT)
|
||
|
|
||
|
def neighbors(self, node):
|
||
|
"""Returns all nodes that are directly accessible from given node.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
node : hashable
|
||
|
Given node identifier.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
list of hashable
|
||
|
Nodes directly accessible from given `node`.
|
||
|
|
||
|
"""
|
||
|
return self.node_neighbors[node]
|
||
|
|
||
|
def has_node(self, node):
|
||
|
"""Returns whether the requested node exists.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
node : hashable
|
||
|
Given node.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
bool
|
||
|
True if `node` exists, False otherwise.
|
||
|
|
||
|
"""
|
||
|
return node in self.node_neighbors
|
||
|
|
||
|
def add_edge(self, edge, wt=1, label='', attrs=None):
|
||
|
"""Adds an edge to the graph connecting two nodes.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
wt : float, optional
|
||
|
Weight of new edge.
|
||
|
label : str, optional
|
||
|
Edge label.
|
||
|
attrs : list, optional
|
||
|
Node attributes specified as (attribute, value).
|
||
|
|
||
|
Raises
|
||
|
------
|
||
|
ValueError
|
||
|
If `edge` already exists in graph.
|
||
|
|
||
|
"""
|
||
|
if attrs is None:
|
||
|
attrs = []
|
||
|
u, v = edge
|
||
|
if v not in self.node_neighbors[u] and u not in self.node_neighbors[v]:
|
||
|
self.node_neighbors[u].append(v)
|
||
|
if u != v:
|
||
|
self.node_neighbors[v].append(u)
|
||
|
|
||
|
self.add_edge_attributes((u, v), attrs)
|
||
|
self.set_edge_properties((u, v), label=label, weight=wt)
|
||
|
else:
|
||
|
raise ValueError("Edge (%s, %s) already in graph" % (u, v))
|
||
|
|
||
|
def add_node(self, node, attrs=None):
|
||
|
"""Adds given node to the graph.
|
||
|
|
||
|
Note
|
||
|
----
|
||
|
While nodes can be of any type, it's strongly recommended
|
||
|
to use only numbers and single-line strings as node identifiers if you
|
||
|
intend to use write().
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
node : hashable
|
||
|
Given node.
|
||
|
attrs : list of (hashable, hashable), optional
|
||
|
Node attributes specified as (attribute, value)
|
||
|
|
||
|
Raises
|
||
|
------
|
||
|
ValueError
|
||
|
If `node` already exists in graph.
|
||
|
|
||
|
"""
|
||
|
if attrs is None:
|
||
|
attrs = []
|
||
|
if node not in self.node_neighbors:
|
||
|
self.node_neighbors[node] = []
|
||
|
self.node_attr[node] = attrs
|
||
|
else:
|
||
|
raise ValueError("Node %s already in graph" % node)
|
||
|
|
||
|
def nodes(self):
|
||
|
"""Returns all nodes of the graph.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
list of hashable
|
||
|
Nodes of graph.
|
||
|
|
||
|
"""
|
||
|
return list(self.node_neighbors.keys())
|
||
|
|
||
|
def edges(self):
|
||
|
"""Returns all edges of the graph.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
list of (hashable, hashable)
|
||
|
Edges of graph.
|
||
|
|
||
|
"""
|
||
|
return [a for a in self.edge_properties.keys()]
|
||
|
|
||
|
def del_node(self, node):
|
||
|
"""Removes given node and its edges from the graph.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
node : hashable
|
||
|
Given node.
|
||
|
|
||
|
"""
|
||
|
for each in list(self.neighbors(node)):
|
||
|
if each != node:
|
||
|
self.del_edge((each, node))
|
||
|
del self.node_neighbors[node]
|
||
|
del self.node_attr[node]
|
||
|
|
||
|
def get_edge_properties(self, edge):
|
||
|
"""Returns properties of given given edge. If edge doesn't exist
|
||
|
empty dictionary will be returned.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
dict
|
||
|
Properties of graph.
|
||
|
|
||
|
"""
|
||
|
return self.edge_properties.setdefault(edge, {})
|
||
|
|
||
|
def add_edge_attributes(self, edge, attrs):
|
||
|
"""Adds attributes `attrs` to given edge, order of nodes in edge doesn't matter.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
attrs : list
|
||
|
Provided attributes to add.
|
||
|
|
||
|
"""
|
||
|
for attr in attrs:
|
||
|
self.add_edge_attribute(edge, attr)
|
||
|
|
||
|
def add_edge_attribute(self, edge, attr):
|
||
|
"""Adds attribute `attr` to given edge, order of nodes in edge doesn't matter.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
attr : object
|
||
|
Provided attribute to add.
|
||
|
|
||
|
"""
|
||
|
self.edge_attr[edge] = self.edge_attributes(edge) + [attr]
|
||
|
|
||
|
if edge[0] != edge[1]:
|
||
|
self.edge_attr[(edge[1], edge[0])] = self.edge_attributes((edge[1], edge[0])) + [attr]
|
||
|
|
||
|
def edge_attributes(self, edge):
|
||
|
"""Returns attributes of given edge.
|
||
|
|
||
|
Note
|
||
|
----
|
||
|
In case of non existing edge returns empty list.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
list
|
||
|
Attributes of given edge.
|
||
|
|
||
|
"""
|
||
|
try:
|
||
|
return self.edge_attr[edge]
|
||
|
except KeyError:
|
||
|
return []
|
||
|
|
||
|
def set_edge_properties(self, edge, **properties):
|
||
|
"""Adds `properties` to given edge, order of nodes in edge doesn't matter.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
properties : dict
|
||
|
Properties to add.
|
||
|
|
||
|
"""
|
||
|
self.edge_properties.setdefault(edge, {}).update(properties)
|
||
|
if edge[0] != edge[1]:
|
||
|
self.edge_properties.setdefault((edge[1], edge[0]), {}).update(properties)
|
||
|
|
||
|
def del_edge(self, edge):
|
||
|
"""Removes given edges from the graph.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
"""
|
||
|
u, v = edge
|
||
|
self.node_neighbors[u].remove(v)
|
||
|
self.del_edge_labeling((u, v))
|
||
|
if u != v:
|
||
|
self.node_neighbors[v].remove(u)
|
||
|
self.del_edge_labeling((v, u))
|
||
|
|
||
|
def del_edge_labeling(self, edge):
|
||
|
"""Removes attributes and properties of given edge.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
edge : (hashable, hashable)
|
||
|
Given edge.
|
||
|
|
||
|
"""
|
||
|
keys = [edge, edge[::-1]]
|
||
|
|
||
|
for key in keys:
|
||
|
for mapping in [self.edge_properties, self.edge_attr]:
|
||
|
try:
|
||
|
del mapping[key]
|
||
|
except KeyError:
|
||
|
pass
|