started operations

This commit is contained in:
John Lancaster
2026-02-21 14:44:45 -06:00
parent 8ec77b51fe
commit b2b90555c2
5 changed files with 67 additions and 3 deletions

View File

@@ -54,7 +54,7 @@ select = [
"SIM", # flake8-simplify "SIM", # flake8-simplify
] ]
extend-fixable = ["ALL"] extend-fixable = ["ALL"]
ignore = ["UP046"] ignore = ["UP046", "UP047"]
[tool.ruff.lint.isort] [tool.ruff.lint.isort]
known-first-party = ["daglib"] known-first-party = ["daglib"]

View File

@@ -5,9 +5,9 @@ from collections.abc import Callable, Iterable, Iterator, MutableMapping
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Generic, TypeVar from typing import Generic, TypeVar
from .operations import transitive_closure
from .set import DAGSetView from .set import DAGSetView
from .typing import dictset
type dictset = defaultdict[T, set[T]]
T = TypeVar("T") T = TypeVar("T")
@@ -115,3 +115,13 @@ class DAG(Generic[T], MutableMapping[T, DAGSetView[T]]):
self._succ.pop(u, None) self._succ.pop(u, None)
self._pred.pop(u, None) self._pred.pop(u, None)
def subgraph(self, sub: Iterable[T]) -> dictset[T]:
closure = transitive_closure(self, sub)
subgraph = DAG[T]()
for n, deps in self._succ.items():
if n in closure:
subgraph[n] += deps & closure
subgraph.on_add = self.on_add
subgraph.on_remove = self.on_remove
return subgraph

43
src/daglib/operations.py Normal file
View File

@@ -0,0 +1,43 @@
from __future__ import annotations
from collections.abc import Iterable
from typing import TYPE_CHECKING, Any, TypeVar
from .util import ensure_set
if TYPE_CHECKING:
from .dag import DAG
from .typing import dictset
T = TypeVar("T")
def all_nodes(graph: DAG) -> set:
return set(graph.keys()) | set(graph.reverse.keys())
def transitive_closure(graph: DAG[T], sub: T | Iterable[T], *, include_self: bool = False):
sub = ensure_set(sub)
stack = list(sub)
seen = set()
while stack:
n = stack.pop()
if n in seen:
continue
seen.add(n)
stack.extend(graph.get(n, []))
if include_self:
seen.update(sub)
return seen
def slice_subgraph(graph: DAG, sub: Any) -> dictset:
closure = transitive_closure(graph, sub)
return {n: d & closure for n in closure if (d := set(graph[n]))}
def topological_sort(graph: DAG, *, reverse: bool = False) -> list:
return list(graph.keys())

10
src/daglib/typing.py Normal file
View File

@@ -0,0 +1,10 @@
from __future__ import annotations
from collections import defaultdict
from collections.abc import MutableMapping, MutableSet
from typing import TypeVar
T = TypeVar("T")
type dictset = defaultdict[T, set[T]]
type DAGType = MutableMapping[T, MutableSet[T]]

View File

@@ -301,6 +301,7 @@ class TestDAGComplexScenarios:
g["foo"] -= {"bar"} g["foo"] -= {"bar"}
assert len(g["foo"]) == 2 assert len(g["foo"]) == 2
assert "bar" not in g["foo"] assert "bar" not in g["foo"]
assert set(g["foo"]) == {"baz", "d"}
def test_type_hints_with_ints(self) -> None: def test_type_hints_with_ints(self) -> None:
g = DAG[int]() g = DAG[int]()