Spaces:
No application file
No application file
| # Copyright 2002 by Tarjei Mikkelsen. All rights reserved. | |
| # Revisions copyright 2018 by Maximilian Greil. All rights reserved. | |
| # | |
| # This file is part of the Biopython distribution and governed by your | |
| # choice of the "Biopython License Agreement" or the "BSD 3-Clause License". | |
| # Please see the LICENSE file that should have been included as part of this | |
| # package. | |
| """get/set abstraction for graph representation.""" | |
| from functools import reduce | |
| class Graph: | |
| """A directed graph abstraction with labeled edges.""" | |
| def __init__(self, nodes=()): | |
| """Initialize a new Graph object.""" | |
| self._adjacency_list = {} # maps parent -> set of child objects | |
| for n in nodes: | |
| self._adjacency_list[n] = set() | |
| self._label_map = {} # maps label -> set of (parent, child) pairs | |
| self._edge_map = {} # maps (parent, child) pair -> label | |
| def __eq__(self, g): | |
| """Return true if g is equal to this graph.""" | |
| return ( | |
| isinstance(g, Graph) | |
| and self._adjacency_list == g._adjacency_list | |
| and self._label_map == g._label_map | |
| and self._edge_map == g._edge_map | |
| ) | |
| def __repr__(self): | |
| """Return a unique string representation of this graph.""" | |
| s = "<Graph: " | |
| for key in sorted(self._adjacency_list): | |
| values = sorted( | |
| (x, self._edge_map[(key, x)]) for x in list(self._adjacency_list[key]) | |
| ) | |
| s += f"({key!r}: {','.join(repr(v) for v in values)})" | |
| return s + ">" | |
| def __str__(self): | |
| """Return a concise string description of this graph.""" | |
| nodenum = len(self._adjacency_list) | |
| edgenum = reduce( | |
| lambda x, y: x + y, [len(v) for v in self._adjacency_list.values()] | |
| ) | |
| labelnum = len(self._label_map) | |
| return ( | |
| "<Graph: " | |
| + str(nodenum) | |
| + " node(s), " | |
| + str(edgenum) | |
| + " edge(s), " | |
| + str(labelnum) | |
| + " unique label(s)>" | |
| ) | |
| def add_node(self, node): | |
| """Add a node to this graph.""" | |
| if node not in self._adjacency_list: | |
| self._adjacency_list[node] = set() | |
| def add_edge(self, source, to, label=None): | |
| """Add an edge to this graph.""" | |
| if source not in self._adjacency_list: | |
| raise ValueError("Unknown <from> node: " + str(source)) | |
| if to not in self._adjacency_list: | |
| raise ValueError("Unknown <to> node: " + str(to)) | |
| if (source, to) in self._edge_map: | |
| raise ValueError(str(source) + " -> " + str(to) + " exists") | |
| self._adjacency_list[source].add(to) | |
| if label not in self._label_map: | |
| self._label_map[label] = set() | |
| self._label_map[label].add((source, to)) | |
| self._edge_map[(source, to)] = label | |
| def child_edges(self, parent): | |
| """Return a list of (child, label) pairs for parent.""" | |
| if parent not in self._adjacency_list: | |
| raise ValueError("Unknown <parent> node: " + str(parent)) | |
| return [ | |
| (x, self._edge_map[(parent, x)]) | |
| for x in sorted(self._adjacency_list[parent]) | |
| ] | |
| def children(self, parent): | |
| """Return a list of unique children for parent.""" | |
| return sorted(self._adjacency_list[parent]) | |
| def edges(self, label): | |
| """Return a list of all the edges with this label.""" | |
| if label not in self._label_map: | |
| raise ValueError("Unknown label: " + str(label)) | |
| return sorted(self._label_map[label]) | |
| def labels(self): | |
| """Return a list of all the edge labels in this graph.""" | |
| return sorted(self._label_map.keys()) | |
| def nodes(self): | |
| """Return a list of the nodes in this graph.""" | |
| return list(self._adjacency_list.keys()) | |
| def parent_edges(self, child): | |
| """Return a list of (parent, label) pairs for child.""" | |
| if child not in self._adjacency_list: | |
| raise ValueError("Unknown <child> node: " + str(child)) | |
| parents = [] | |
| for parent, children in self._adjacency_list.items(): | |
| for x in children: | |
| if x == child: | |
| parents.append((parent, self._edge_map[(parent, child)])) | |
| return sorted(parents) | |
| def parents(self, child): | |
| """Return a list of unique parents for child.""" | |
| return sorted({x[0] for x in self.parent_edges(child)}) | |
| def remove_node(self, node): | |
| """Remove node and all edges connected to it.""" | |
| if node not in self._adjacency_list: | |
| raise ValueError("Unknown node: " + str(node)) | |
| # remove node (and all out-edges) from adjacency list | |
| del self._adjacency_list[node] | |
| # remove all in-edges from adjacency list | |
| for n in self._adjacency_list.keys(): | |
| self._adjacency_list[n] = {x for x in self._adjacency_list[n] if x != node} | |
| # remove all referring pairs in label map | |
| for label in list(self._label_map.keys()): # we're editing this! | |
| lm = { | |
| x for x in self._label_map[label] if (x[0] != node) and (x[1] != node) | |
| } | |
| # remove the entry completely if the label is now unused | |
| if lm: | |
| self._label_map[label] = lm | |
| else: | |
| del self._label_map[label] | |
| # remove all referring entries in edge map | |
| for edge in list(self._edge_map.keys()): # we're editing this! | |
| if edge[0] == node or edge[1] == node: | |
| del self._edge_map[edge] | |
| def remove_edge(self, parent, child, label): | |
| """Remove edge (NOT IMPLEMENTED).""" | |
| # hm , this is a multigraph - how should this be implemented? | |
| raise NotImplementedError("remove_edge is not yet implemented") | |