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())