From e3f77ec1fca4f9e239ec7f6cf40873533cbfc93e Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sat, 12 Mar 2022 20:52:58 +0000 Subject: [PATCH 1/5] seen packages is a set --- src/poetry/puzzle/solver.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/poetry/puzzle/solver.py b/src/poetry/puzzle/solver.py index 32bd327a7be..24be7505b24 100644 --- a/src/poetry/puzzle/solver.py +++ b/src/poetry/puzzle/solver.py @@ -140,11 +140,12 @@ def _solve(self, use_latest: list[str] = None) -> tuple[list[Package], list[int] except SolveFailure as e: raise SolverProblemError(e) - # NOTE passing explicit empty array for seen to reset between invocations during + # NOTE passing explicit empty set for seen to reset between invocations during # update + install cycle results = dict( depth_first_search( - PackageNode(self._package, packages, seen=[]), aggregate_package_nodes + PackageNode(self._package, packages, seen=set()), + aggregate_package_nodes, ) ) @@ -258,7 +259,7 @@ def __init__( self, package: Package, packages: list[Package], - seen: list[Package], + seen: set[Package], previous: PackageNode | None = None, previous_dep: None | ( @@ -309,8 +310,8 @@ def reachable(self) -> list[PackageNode]: # skip already traversed packages if self.package in self.seen: return [] - else: - self.seen.append(self.package) + + self.seen.add(self.package) if ( self.dep From b969ae2ed8cb1e93aeb47fe58117b5bc631f5509 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 13 Mar 2022 11:06:02 +0000 Subject: [PATCH 2/5] simplify 'visited' in solver depth-first-search --- src/poetry/puzzle/solver.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/poetry/puzzle/solver.py b/src/poetry/puzzle/solver.py index 24be7505b24..c48b4c1d1b7 100644 --- a/src/poetry/puzzle/solver.py +++ b/src/poetry/puzzle/solver.py @@ -1,6 +1,5 @@ from __future__ import annotations -import enum import time from collections import defaultdict @@ -195,17 +194,11 @@ def __str__(self) -> str: return str(self.id) -class VisitedState(enum.Enum): - Unvisited = 0 - PartiallyVisited = 1 - Visited = 2 - - def depth_first_search( source: PackageNode, aggregator: Callable ) -> list[tuple[Package, int]]: back_edges: dict[DFSNodeID, list[PackageNode]] = defaultdict(list) - visited: dict[DFSNodeID, VisitedState] = {} + visited: set[DFSNodeID] = set() topo_sorted_nodes: list[PackageNode] = [] dfs_visit(source, back_edges, visited, topo_sorted_nodes) @@ -233,23 +226,17 @@ def depth_first_search( def dfs_visit( node: PackageNode, back_edges: dict[DFSNodeID, list[PackageNode]], - visited: dict[DFSNodeID, VisitedState], + visited: set[DFSNodeID], sorted_nodes: list[PackageNode], ) -> bool: - if visited.get(node.id, VisitedState.Unvisited) == VisitedState.Visited: - return True - if visited.get(node.id, VisitedState.Unvisited) == VisitedState.PartiallyVisited: - # We have a circular dependency. - # Since the dependencies are resolved we can - # simply skip it because we already have it + if node.id in visited: return True + visited.add(node.id) - visited[node.id] = VisitedState.PartiallyVisited for neighbor in node.reachable(): back_edges[neighbor.id].append(node) if not dfs_visit(neighbor, back_edges, visited, sorted_nodes): return False - visited[node.id] = VisitedState.Visited sorted_nodes.insert(0, node) return True From 56d1b2ba86b42da30fe6afd1a53f89ea6ee72b1d Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 13 Mar 2022 11:06:35 +0000 Subject: [PATCH 3/5] remove pointless return code --- src/poetry/puzzle/solver.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/poetry/puzzle/solver.py b/src/poetry/puzzle/solver.py index c48b4c1d1b7..d8116ac576c 100644 --- a/src/poetry/puzzle/solver.py +++ b/src/poetry/puzzle/solver.py @@ -228,17 +228,15 @@ def dfs_visit( back_edges: dict[DFSNodeID, list[PackageNode]], visited: set[DFSNodeID], sorted_nodes: list[PackageNode], -) -> bool: +) -> None: if node.id in visited: - return True + return visited.add(node.id) for neighbor in node.reachable(): back_edges[neighbor.id].append(node) - if not dfs_visit(neighbor, back_edges, visited, sorted_nodes): - return False + dfs_visit(neighbor, back_edges, visited, sorted_nodes) sorted_nodes.insert(0, node) - return True class PackageNode(DFSNode): From 3a79837d1bac31cb27d2cf3048c8d4eb7a17a5d6 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 13 Mar 2022 14:16:23 +0000 Subject: [PATCH 4/5] node.reachable() is always empty after the search because node.seen has been fully populated --- src/poetry/puzzle/solver.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/poetry/puzzle/solver.py b/src/poetry/puzzle/solver.py index d8116ac576c..97d548c3e8e 100644 --- a/src/poetry/puzzle/solver.py +++ b/src/poetry/puzzle/solver.py @@ -205,10 +205,8 @@ def depth_first_search( # Combine the nodes by name combined_nodes = defaultdict(list) - name_children = defaultdict(list) for node in topo_sorted_nodes: node.visit(back_edges[node.id]) - name_children[node.name].extend(node.reachable()) combined_nodes[node.name].append(node) combined_topo_sorted_nodes = [ @@ -217,10 +215,7 @@ def depth_first_search( if node.name in combined_nodes ] - return [ - aggregator(nodes, name_children[nodes[0].name]) - for nodes in combined_topo_sorted_nodes - ] + return [aggregator(nodes) for nodes in combined_topo_sorted_nodes] def dfs_visit( @@ -355,19 +350,15 @@ def visit(self, parents: list[PackageNode]) -> None: ) -def aggregate_package_nodes( - nodes: list[PackageNode], children: list[PackageNode] -) -> tuple[Package, int]: +def aggregate_package_nodes(nodes: list[PackageNode]) -> tuple[Package, int]: package = nodes[0].package depth = max(node.depth for node in nodes) groups: list[str] = [] for node in nodes: groups.extend(node.groups) - category = ( - "main" if any("default" in node.groups for node in children + nodes) else "dev" - ) - optional = all(node.optional for node in children + nodes) + category = "main" if any("default" in node.groups for node in nodes) else "dev" + optional = all(node.optional for node in nodes) for node in nodes: node.depth = depth node.category = category From 4cf4f0c67800ea1fc50ebaa58b871bf6f0a17a38 Mon Sep 17 00:00:00 2001 From: David Hotham Date: Sun, 13 Mar 2022 14:17:56 +0000 Subject: [PATCH 5/5] remove node.seen in solver --- src/poetry/puzzle/solver.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/poetry/puzzle/solver.py b/src/poetry/puzzle/solver.py index 97d548c3e8e..1fe47fe4350 100644 --- a/src/poetry/puzzle/solver.py +++ b/src/poetry/puzzle/solver.py @@ -139,12 +139,9 @@ def _solve(self, use_latest: list[str] = None) -> tuple[list[Package], list[int] except SolveFailure as e: raise SolverProblemError(e) - # NOTE passing explicit empty set for seen to reset between invocations during - # update + install cycle results = dict( depth_first_search( - PackageNode(self._package, packages, seen=set()), - aggregate_package_nodes, + PackageNode(self._package, packages), aggregate_package_nodes ) ) @@ -239,7 +236,6 @@ def __init__( self, package: Package, packages: list[Package], - seen: set[Package], previous: PackageNode | None = None, previous_dep: None | ( @@ -260,7 +256,6 @@ def __init__( ) -> None: self.package = package self.packages = packages - self.seen = seen self.previous = previous self.previous_dep = previous_dep @@ -287,12 +282,6 @@ def __init__( def reachable(self) -> list[PackageNode]: children: list[PackageNode] = [] - # skip already traversed packages - if self.package in self.seen: - return [] - - self.seen.add(self.package) - if ( self.dep and self.previous_dep @@ -329,7 +318,6 @@ def reachable(self) -> list[PackageNode]: PackageNode( pkg, self.packages, - self.seen, self, dependency, self.dep or dependency,