Graph algorithms: Requirements Resolution
From wikinotes
Tutorials
electricmonk https://www.electricmonk.nl/docs/dependency_resolving_algorithm/dependency_resolving_algorithm.html
Overview
1. Recurse to the leaf-node(s) of each pathway through graph.
Recurse to the leaf-node(s) of each pathway through graph. Do not modify requirements.# step 1 + | + / +2.
On the way back upwards from the bottom, append nodes. This way the deepest nodes (with no requirements) are added first, only afterwards followed by nodes with requirements.# step 2 + | + / o # step 3 + | + /| o o # step 4 + | + /|\ o o o # step 5 + | o /|\ o o o3.
We can ignore repeated nodes, since we know it is impossible for their dependencies not to have already been added.A ++ / \ B C F + \ + / | \ | + D +ENavigating it with left-first works
# left paths reqs = [D] # children of B reqs.extend([B]) # child of A (left) # right paths reqs.extend([F]) # children of E (skip B, already seen) reqs.extend([E]) # children of C reqs.extend([C]) # child of A (right) # top reqs.append(A) reqs = [D, B, F, E, C, A]Navigating it right-first also works
# right paths reqs = [F] # child of E (right) reqs.extend([D]) # child of B reqs.extend([B]) # child of E (left) reqs.extend([E]) # child of C reqs.extend([C]) # child of A (right) # left paths reqs.extend([D]) # children of B reqs.extend([]) # child of A (left) (skip B, already seen) # top reqs.append(A) reqs = [F, D, B, E, C, A]
Code
class InvalidGraph(Exception): pass class Requirement(object): @property def name(self): return self._name def __init__(self, name): """ Constructor. Args: name (str): ``(ex: 'mysoftware')`` name or unique-identifier of this requirement. """ super(Requirement, self).__init__() self._name = name self._requires = [] def __eq__(self, requirement): """ Does `requirement` name match this object's name? Args: requirement (Requirement): requirement you are checking for in this object's requirements Returns: bool: True if equal """ self._validate_type_requirement(requirement) return self.name == requirement.name def __repr__(self): return '<Requirement({}) at {}>'.format(self.name, hex(id(self))) def __str__(self): """ When converted to a string, returns name. """ return self.name def __neq__(self, requirement): """ Does `requirement` name match this object's name? Args: requirement (Requirement): requirement you are checking for in this object's requirements Returns: bool: True if not-equal """ self._validate_type_requirement(requirement) return not self.__eq__(requirement) def __contains__(self, requirement): """ Does `requirement` exist anywhere in this requirements-tree? Args: requirement (Requirement): requirement you are checking for in this object's requirements """ self._validate_type_requirement(requirement) requirements = self.resolve() return requirement in requirements def _validate_type_requirement(self, requirement): """ Args: requirement (Requirement): object being validated Raises: TypeError: if provided `requirement` is not correct type """ if not isinstance(requirement, Requirement): msg = ('expected `requirement` to be of type {}. \n' 'Received: {}').format(self.__class__.__name__, repr(type(requirement))) raise TypeError(msg) def add_requirement(self, requirement): """ Add a requirement to this node. Args: requirement (Requirement): a requirement object """ self._validate_type_requirement(requirement) if requirement in self._requires: return self._requires.append(requirement) def resolve(self, _requirements=None, _seen=None): #: final list of requirements (added after children) if _requirements is None: _requirements = [] #: detects cyclical dependencies (added before children) if _seen is None: _seen = [] _seen.append(self) for requirement in self._requires: # skip requirements that have been added if requirement in _requirements: continue # detect cycle if requirement in _seen: msg = 'Dependency-Cycle detected involving requirement: "{}"'.format(repr(requirement)) raise InvalidGraph(msg) requirement.resolve(_requirements, _seen) _requirements.append(self) return _requirements