183 lines
5.2 KiB
Python
183 lines
5.2 KiB
Python
|
from collections import deque
|
||
|
|
||
|
|
||
|
class Tree(object):
|
||
|
def __init__(self,
|
||
|
node_id,
|
||
|
children=None,
|
||
|
data=None,
|
||
|
props=None,
|
||
|
edge_data=None,
|
||
|
edge_props=None):
|
||
|
"""
|
||
|
A class to facilitate tree manipulation in Cytoscape.
|
||
|
:param node_id: The ID of this tree, passed to the node data dict
|
||
|
:param children: The children of this tree, also Tree objects
|
||
|
:param data: Dictionary passed to this tree's node data dict
|
||
|
:param props: Dictionary passed to this tree's node dict, containing the node's props
|
||
|
:param edge_data: Dictionary passed to the data dict of the edge connecting this tree to its
|
||
|
parent
|
||
|
:param edge_props: Dictionary passed to the dict of the edge connecting this tree to its
|
||
|
parent
|
||
|
"""
|
||
|
if children is None:
|
||
|
children = []
|
||
|
if data is None:
|
||
|
data = {}
|
||
|
if props is None:
|
||
|
props = {}
|
||
|
if edge_data is None:
|
||
|
edge_data = {}
|
||
|
if edge_props is None:
|
||
|
edge_props = {}
|
||
|
|
||
|
self.node_id = node_id
|
||
|
self.children = children
|
||
|
self.data = data
|
||
|
self.props = props
|
||
|
self.edge_data = edge_data
|
||
|
self.edge_props = edge_props
|
||
|
self.index = {}
|
||
|
|
||
|
def _dfs(self, search_id):
|
||
|
if self.node_id == search_id:
|
||
|
return self
|
||
|
elif self.is_leaf():
|
||
|
return None
|
||
|
else:
|
||
|
for child in self.children:
|
||
|
result = child.dfs()
|
||
|
if result:
|
||
|
return result
|
||
|
|
||
|
return None
|
||
|
|
||
|
def _bfs(self, search_id):
|
||
|
stack = deque([self])
|
||
|
|
||
|
while stack:
|
||
|
tree = stack.popleft()
|
||
|
|
||
|
if tree.node_id == search_id:
|
||
|
return tree
|
||
|
|
||
|
if not tree.is_leaf():
|
||
|
for child in tree.children:
|
||
|
stack.append(child)
|
||
|
|
||
|
return None
|
||
|
|
||
|
def is_leaf(self):
|
||
|
"""
|
||
|
:return: If the Tree is a leaf or not
|
||
|
"""
|
||
|
return not self.children
|
||
|
|
||
|
def add_children(self, children):
|
||
|
"""
|
||
|
Add one or more children to the current children of a Tree.
|
||
|
:param children: List of Tree objects (one object or more)
|
||
|
"""
|
||
|
self.children.extend(children)
|
||
|
|
||
|
def get_edges(self):
|
||
|
"""
|
||
|
Get all the edges of the tree in Cytoscape JSON format.
|
||
|
:return: List of dictionaries, each specifying an edge
|
||
|
"""
|
||
|
edges = []
|
||
|
|
||
|
for child in self.children:
|
||
|
di = {
|
||
|
'data': {
|
||
|
'source': self.node_id,
|
||
|
'target': child.node_id
|
||
|
}
|
||
|
}
|
||
|
di['data'].update(child.edge_data)
|
||
|
di.update(child.edge_props)
|
||
|
edges.append(di)
|
||
|
|
||
|
for child in self.children:
|
||
|
edges.extend(child.get_edges())
|
||
|
|
||
|
return edges
|
||
|
|
||
|
def get_nodes(self):
|
||
|
"""
|
||
|
Get all the nodes of the tree in Cytoscape JSON format.
|
||
|
:return: List of dictionaries, each specifying a node
|
||
|
"""
|
||
|
di = {
|
||
|
'data': {
|
||
|
'id': self.node_id
|
||
|
}
|
||
|
}
|
||
|
|
||
|
di['data'].update(self.data)
|
||
|
di.update(self.props)
|
||
|
nodes = [di]
|
||
|
|
||
|
for child in self.children:
|
||
|
nodes.extend(child.get_nodes())
|
||
|
|
||
|
return nodes
|
||
|
|
||
|
def get_elements(self):
|
||
|
"""
|
||
|
Get all the elements of the tree in Cytoscape JSON format.
|
||
|
:return: List of dictionaries, each specifying an element
|
||
|
"""
|
||
|
return self.get_nodes() + self.get_edges()
|
||
|
|
||
|
def find_by_id(self, search_id, method='bfs'):
|
||
|
"""
|
||
|
Find a Tree object by its ID.
|
||
|
:param search_id: the queried ID
|
||
|
:param method: Which traversal method to use. Either "bfs" or "dfs"
|
||
|
:return: Tree object if found, None otherwise
|
||
|
"""
|
||
|
method = method.lower()
|
||
|
|
||
|
if method == 'bfs':
|
||
|
return self._bfs(search_id)
|
||
|
elif method == 'dfs':
|
||
|
return self._dfs(search_id)
|
||
|
else:
|
||
|
raise ValueError('Unknown traversal method')
|
||
|
|
||
|
def create_index(self):
|
||
|
"""
|
||
|
Generate the index of a Tree, and set it in place. If there was a previous index, it is
|
||
|
erased. This uses a BFS traversal. Please note that when a child is added to the tree,
|
||
|
the index is not regenerated. Furthermore, an index assigned to a parent cannot be
|
||
|
accessed by its children, and vice-versa.
|
||
|
:return: Dictionary mapping node_id to Tree object
|
||
|
"""
|
||
|
stack = deque([self])
|
||
|
self.index = {}
|
||
|
|
||
|
while stack:
|
||
|
tree = stack.popleft()
|
||
|
self.index[tree.node_id] = tree
|
||
|
|
||
|
if not tree.is_leaf():
|
||
|
for child in tree.children:
|
||
|
stack.append(child)
|
||
|
|
||
|
return self.index
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
import pprint
|
||
|
|
||
|
t1 = Tree('a', data={'hello': 'goodbye'}, children=[
|
||
|
Tree('b', edge_data={'foo': 'bar'}, edge_props={'classes': 'directed'}),
|
||
|
Tree('c', props={'selected': True})
|
||
|
])
|
||
|
|
||
|
print("Nodes:")
|
||
|
pprint.pprint(t1.get_nodes())
|
||
|
print('\nEdges:')
|
||
|
pprint.pprint(t1.get_edges())
|