From aaf2f015f67224bc373a0794e4f296173f3d9510 Mon Sep 17 00:00:00 2001 From: Stefan Urbanek Date: Tue, 12 Nov 2024 22:56:07 +0100 Subject: [PATCH] Graph clean-up - Renamed ObjectGraph to GraphProtocol - Added small Graph structure - Changed topological sort to use all nodes and edges of a graph - Removed Mutable Graph - Removed neighbourhood selector --- .../Constraints/Constraint+Node.swift | 12 +- .../PoieticCore/Constraints/Constraint.swift | 6 +- Sources/PoieticCore/Design/Frame.swift | 18 +-- .../PoieticCore/Design/ObjectSnapshot.swift | 27 ++++ Sources/PoieticCore/Design/StableFrame.swift | 3 +- .../TransientFrame+Graph.swift} | 52 ++----- .../PoieticCore/Documentation.docc/Graphs.md | 17 +- .../Documentation.docc/MetamodelAndTypes.md | 1 + .../PoieticCore/Graph/Graph+algorithms.swift | 139 +++++------------ .../Graph/{ObjectGraph.swift => Graph.swift} | 147 ++++++++---------- Sources/PoieticCore/Graph/Neighborhood.swift | 32 +--- 11 files changed, 165 insertions(+), 289 deletions(-) rename Sources/PoieticCore/{Graph/MutableGraph.swift => Design/TransientFrame+Graph.swift} (63%) rename Sources/PoieticCore/Graph/{ObjectGraph.swift => Graph.swift} (50%) diff --git a/Sources/PoieticCore/Constraints/Constraint+Node.swift b/Sources/PoieticCore/Constraints/Constraint+Node.swift index 8654a2d..48e6b7b 100644 --- a/Sources/PoieticCore/Constraints/Constraint+Node.swift +++ b/Sources/PoieticCore/Constraints/Constraint+Node.swift @@ -6,7 +6,8 @@ // public final class UniqueNeighbourRequirement: ConstraintRequirement { - public let selector: NeighborhoodSelector + public let predicate: Predicate + public let direction: EdgeDirection public let isRequired: Bool /// Creates a constraint for unique neighbour. @@ -21,8 +22,9 @@ public final class UniqueNeighbourRequirement: ConstraintRequirement { /// matching node /// - required: Wether the unique neighbour is required. /// - public init(_ selector: NeighborhoodSelector, required: Bool=false) { - self.selector = selector + public init(_ predicate: Predicate, direction: EdgeDirection = .outgoing, required: Bool=false) { + self.predicate = predicate + self.direction = direction self.isRequired = required } @@ -32,7 +34,9 @@ public final class UniqueNeighbourRequirement: ConstraintRequirement { guard $0.structure.type == .node else { return false } - let hood = frame.hood($0.id, selector: self.selector) + let hood = frame.hood($0.id, direction: direction) { edge in + predicate.match(frame: frame, object: edge.snapshot) + } let count = hood.edges.count return count > 1 || (count == 0 && isRequired) diff --git a/Sources/PoieticCore/Constraints/Constraint.swift b/Sources/PoieticCore/Constraints/Constraint.swift index 685ff81..5eb9b27 100644 --- a/Sources/PoieticCore/Constraints/Constraint.swift +++ b/Sources/PoieticCore/Constraints/Constraint.swift @@ -52,10 +52,8 @@ public struct ConstraintViolation: Error, CustomDebugStringConvertible { /// name: "one_parameter_for_graphical_function", /// match: IsTypePredicate(ObjectType.GraphicalFunction), /// requirement: UniqueNeighbourRequirement( -/// NeighborhoodSelector( -/// predicate: IsTypePredicate(ObjectType.Parameter), -/// direction: .incoming -/// ), +/// IsTypePredicate(ObjectType.Parameter), +/// direction: .incoming, /// required: false /// ) /// ) diff --git a/Sources/PoieticCore/Design/Frame.swift b/Sources/PoieticCore/Design/Frame.swift index e26ec1e..83ec4b1 100644 --- a/Sources/PoieticCore/Design/Frame.swift +++ b/Sources/PoieticCore/Design/Frame.swift @@ -10,7 +10,7 @@ /// Fame Base is a protocol for all version frame types: ``TransientFrame`` and /// ``StableFrame`` /// -public protocol Frame: ObjectGraph where Node == Snapshot, Edge == EdgeSnapshot { +public protocol Frame: GraphProtocol where Node == Snapshot, Edge == EdgeSnapshot { associatedtype Snapshot: ObjectSnapshot /// Design to which the frame belongs. var design: Design { get } @@ -303,22 +303,6 @@ extension Frame { public func top() -> [Snapshot] { self.filter { $0.parent == nil } } - - public func hood(_ nodeID: ObjectID, selector: NeighborhoodSelector) -> Neighborhood { - let edges: [Edge] - switch selector.direction { - case .incoming: edges = incoming(nodeID) - case .outgoing: edges = outgoing(nodeID) - } - let filtered: [Edge] = edges.filter { - selector.predicate.match(frame: self, object: $0.snapshot) - } - - return Neighborhood(graph: self, - nodeID: nodeID, - direction: selector.direction, - edges: filtered) - } } diff --git a/Sources/PoieticCore/Design/ObjectSnapshot.swift b/Sources/PoieticCore/Design/ObjectSnapshot.swift index 3608e64..e1ed34b 100644 --- a/Sources/PoieticCore/Design/ObjectSnapshot.swift +++ b/Sources/PoieticCore/Design/ObjectSnapshot.swift @@ -246,3 +246,30 @@ extension ObjectSnapshot { return Array(refs) } } + +// MARK: - Graph Protocol + +// TODO: Find a more descriptive name. +/// Wrapper of an object snapshot presented as an edge. +/// +public struct EdgeSnapshot: EdgeProtocol { + public typealias NodeID = ObjectID + public let snapshot: any ObjectSnapshot + public let origin: ObjectID + public let target: ObjectID + + public init?(_ snapshot: any ObjectSnapshot) { + guard case let .edge(origin, target) = snapshot.structure else { + return nil + } + + self.snapshot = snapshot + self.origin = origin + self.target = target + } + + public var id: ObjectID { snapshot.id } + public var type: ObjectType { snapshot.type } + public var name: String? { snapshot.name } +} + diff --git a/Sources/PoieticCore/Design/StableFrame.swift b/Sources/PoieticCore/Design/StableFrame.swift index 8acab64..97ebe9d 100644 --- a/Sources/PoieticCore/Design/StableFrame.swift +++ b/Sources/PoieticCore/Design/StableFrame.swift @@ -84,8 +84,7 @@ public final class StableFrame: Frame { /// Get an immutable graph view of the frame. /// - public var graph: any ObjectGraph { + public var graph: any GraphProtocol { return self } } - diff --git a/Sources/PoieticCore/Graph/MutableGraph.swift b/Sources/PoieticCore/Design/TransientFrame+Graph.swift similarity index 63% rename from Sources/PoieticCore/Graph/MutableGraph.swift rename to Sources/PoieticCore/Design/TransientFrame+Graph.swift index 865968a..1c570db 100644 --- a/Sources/PoieticCore/Graph/MutableGraph.swift +++ b/Sources/PoieticCore/Design/TransientFrame+Graph.swift @@ -5,45 +5,13 @@ // Created by Stefan Urbanek on 21/08/2023. // -/// Protocol -public protocol MutableGraph: ObjectGraph { - // Object creation - @discardableResult - func createNode(_ type: ObjectType, name: String?, attributes: [String:Variant]) -> MutableObject - - @discardableResult - func createEdge(_ type: ObjectType, origin: ObjectID, target: ObjectID, - attributes: [String:Variant]) -> MutableObject - - /// Remove all nodes and edges from the graph. - func removeAll() - - /// Remove a node from the graph and return a list of edges that were - /// removed together with the node. +extension TransientFrame /* MutableGraph (no longer formally present) */ { + /// Convenience method to create an edge. /// - func remove(node nodeID: ObjectID) - - /// Remove an edge from the graph. + /// If the object name is provided, then attribute `name` of the + /// object is set. Replaces `name` attribute in the `attributes` dictionary. /// - func remove(edge edgeID: ObjectID) -} - -extension MutableGraph { - public func removeAll() { - for edge in edgeIDs { - remove(edge: edge) - } - for node in nodeIDs { - remove(node: node) - } - } -} - - -/// Graph contained within a mutable frame where the references to the nodes and -/// edges are not directly bound and are resolved at the time of querying. -extension TransientFrame: MutableGraph { - // Object creation + /// - SeeAlso: ``TransientFrame/create(_:id:snapshotID:structure:parent:children:attributes:components:)`` @discardableResult public func createEdge(_ type: ObjectType, origin: ObjectID, @@ -58,22 +26,28 @@ extension TransientFrame: MutableGraph { return snapshot } + /// Convenience method to create an edge. + /// + /// If the object name is provided, then attribute `name` of the + /// object is set. Replaces `name` attribute in the `attributes` dictionary. + /// + /// - SeeAlso: ``TransientFrame/create(_:id:snapshotID:structure:parent:children:attributes:components:)`` @discardableResult public func createEdge(_ type: ObjectType, origin: any ObjectSnapshot, target: any ObjectSnapshot, attributes: [String:Variant] = [:]) -> MutableObject { + // FIXME: [WIP] Still needed? return createEdge(type, origin: origin.id, target: target.id, attributes: attributes) } - /// Creates a new node. + /// Convenience method to a new node. /// /// - Parameters: /// - type: Object type of the newly created node. /// - name: Optional object name. See note below. /// - attributes: Dictionary of attributes to set. - /// - components: List of components assigned with the node. /// /// If the object name is provided, then attribute `name` of the /// object is set. Replaces `name` attribute in the `attributes` dictionary. diff --git a/Sources/PoieticCore/Documentation.docc/Graphs.md b/Sources/PoieticCore/Documentation.docc/Graphs.md index bcbc726..32a7036 100644 --- a/Sources/PoieticCore/Documentation.docc/Graphs.md +++ b/Sources/PoieticCore/Documentation.docc/Graphs.md @@ -6,19 +6,12 @@ Graphs are views that comprise of nodes and edges – connections between nodes. ### Graph, Nodes and Edges -- ``ObjectGraph`` -- ``MutableGraph`` - -- ``Node`` -- ``Edge`` -- ``EdgeType`` -- ``EdgeDirection`` - -- ``topologicalSort(_:edges:)`` - -- ``GraphCycleError`` +- ``GraphProtocol`` +- ``Graph`` +- ``EdgeSnapshot`` +- ``EdgeProtocol`` ### Neighbourhoods - ``Neighborhood`` -- ``NeighborhoodSelector`` +- ``EdgeDirection`` diff --git a/Sources/PoieticCore/Documentation.docc/MetamodelAndTypes.md b/Sources/PoieticCore/Documentation.docc/MetamodelAndTypes.md index 4ff2cac..235925e 100644 --- a/Sources/PoieticCore/Documentation.docc/MetamodelAndTypes.md +++ b/Sources/PoieticCore/Documentation.docc/MetamodelAndTypes.md @@ -106,6 +106,7 @@ examples see ``Constraint``. - ``UniqueNeighbourRequirement`` - ``UniqueProperty`` - ``ConstraintRequirement`` +- ``EdgeEndpointRequirement`` ### Common Components and Types diff --git a/Sources/PoieticCore/Graph/Graph+algorithms.swift b/Sources/PoieticCore/Graph/Graph+algorithms.swift index fa359f7..1e95b48 100644 --- a/Sources/PoieticCore/Graph/Graph+algorithms.swift +++ b/Sources/PoieticCore/Graph/Graph+algorithms.swift @@ -5,139 +5,70 @@ // Created by Stefan Urbanek on 09/09/2022. // -/// An error raised when a cycle is detected in the graph. -/// -public struct GraphCycleError: Error { - - /// List of edges that are part of a cycle - /// - public let edges: [ObjectID] - - /// Create an error object from a list of edges. - /// - /// - Precondition: The list of edges must not be empty. - /// - public init(edges: [ObjectID]) { - precondition(!edges.isEmpty, - "Cycle error must contain at least one edge") - self.edges = edges - } -} - - - -extension ObjectGraph { +extension GraphProtocol { /// Sort nodes topologically. /// - /// - Parameters: - /// - nodes: list of nodes to be sorted - /// - edges: list of edges to be considered during the sorting - /// /// - Returns: Sorted node IDs when there were no issues, or nil if there was a cycle. - /// - Throws: ``GraphCycleError`` when a cycle is detected in the graph. /// - public func topologicalSort(_ toSort: [ObjectID], edges: [Edge]) throws (GraphCycleError) -> [ObjectID] { - var sorted: [ObjectID] = [] - let nodes: [ObjectID] = toSort - - // Create a copy - var edges = edges - + public func topologicalSort() -> [Node.ID]? { + var sorted: [Node.ID] = [] + var edges = self.edges let targets = Set(edges.map {$0.target}) - // S ← Set of all nodes with no incoming edge - var sources: [ObjectID] = nodes.filter { !targets.contains($0) } - - // while S is not empty do + var sources: [Node.ID] = + self.nodes.map { $0.id }.filter { !targets.contains($0) } + while !sources.isEmpty { - // remove a node n from S - let node: ObjectID = sources.removeFirst() - // add n to L + let node: Node.ID = sources.removeFirst() sorted.append(node) let outgoing: [Edge] = edges.filter { $0.origin == node } for edge in outgoing { - // for each node m with an edge e from n to m do - let m: ObjectID = edge.target - - // remove edge e from the graph + let m: Node.ID = edge.target + edges.removeAll { $0.id == edge.id } - // if m has no other incoming edges then if edges.allSatisfy({$0.target != m}) { - // insert m into S sources.append(m) } } } - + if edges.isEmpty { return sorted } else { - throw GraphCycleError(edges: edges.map({ $0.id })) + return nil } } -} - -/// Protocol for objects that represent an edge. -/// -/// - SeeAlso: ``topologicalSort(_:edges:)`` -/// -public protocol EdgeType: Identifiable where ID == ObjectID { - /// Object ID of an edge origin. - var origin: ObjectID { get } - /// Object ID of an edge target. - var target: ObjectID { get } -} - - -/// Sort edges topologically. -/// -/// - Parameters: -/// - toSort: List of node object IDs to be sorted. -/// - edges: List of edges between nodes. -/// -/// - Throws: ``GraphCycleError`` when a cycle is detected. -/// -public func topologicalSort(_ toSort: [ObjectID], edges: [T]) throws (GraphCycleError) -> [ObjectID] { - var sorted: [ObjectID] = [] - let nodes: [ObjectID] = toSort - - // Create a copy - var edges = edges - - let targets = Set(edges.map {$0.target}) - // S ← Set of all nodes with no incoming edge - var sources: [ObjectID] = nodes.filter { !targets.contains($0) } - - // while S is not empty do - while !sources.isEmpty { - // remove a node n from S - let node: ObjectID = sources.removeFirst() - // add n to L - sorted.append(node) + /// Find cycles in a subgraph. + /// + /// - Returns: List of cycles + /// + public func cycles() -> [Edge] { + let nodes: [Node.ID] = self.nodes.map { $0.id } + var edges = self.edges - let outgoing: [T] = edges.filter { $0.origin == node } + let targets = Set(edges.map {$0.target}) + var sources: [Node.ID] = nodes.filter { !targets.contains($0) } - for edge in outgoing { - // for each node m with an edge e from n to m do - let m: ObjectID = edge.target - - // remove edge e from the graph - edges.removeAll { $0.id == edge.id } + while !sources.isEmpty { + let node: Node.ID = sources.removeFirst() + + let outgoing: [Edge] = edges.filter { $0.origin == node } - // if m has no other incoming edges then - if edges.allSatisfy({$0.target != m}) { - // insert m into S - sources.append(m) + for edge in outgoing { + let m: Node.ID = edge.target + + edges.removeAll { $0.id == edge.id } + + if edges.allSatisfy({$0.target != m}) { + sources.append(m) + } } } + + return edges } - if !edges.isEmpty { - throw GraphCycleError(edges: edges.map {$0.id} ) - } - - return sorted } diff --git a/Sources/PoieticCore/Graph/ObjectGraph.swift b/Sources/PoieticCore/Graph/Graph.swift similarity index 50% rename from Sources/PoieticCore/Graph/ObjectGraph.swift rename to Sources/PoieticCore/Graph/Graph.swift index b9e2f76..a28b2a8 100644 --- a/Sources/PoieticCore/Graph/ObjectGraph.swift +++ b/Sources/PoieticCore/Graph/Graph.swift @@ -5,54 +5,26 @@ // Created by Stefan Urbanek on 04/06/2023. // -/// View of an object as an edge. -/// -public struct EdgeSnapshot: EdgeProtocol { - // TODO: Clean-up the types - public let snapshot: any ObjectSnapshot - public let origin: ObjectID - public let target: ObjectID - - public init?(_ snapshot: any ObjectSnapshot) { - guard case let .edge(origin, target) = snapshot.structure else { - return nil - } - - self.snapshot = snapshot - self.origin = origin - self.target = target - } - - public var id: ObjectID { snapshot.id } - public var type: ObjectType { snapshot.type } - public var name: String? { snapshot.name } -} - /// Protocol for edges in a graph. /// -/// - SeeAlso: ``ObjectGraph`` -public protocol EdgeProtocol: Identifiable where ID == ObjectID { +/// - SeeAlso: ``GraphProtocol`` +public protocol EdgeProtocol: Identifiable { + associatedtype NodeID /// Origin of the edge. - var origin: ObjectID { get } + var origin: NodeID { get } /// Target of the edge. - var target: ObjectID { get } + var target: NodeID { get } } -/// Collection of objects as nodes and edges. +/// Protocol for object graphs - nodes connected by edges. /// /// Graphs are used to view interconnected object structures. When you use design frames /// you will benefit from operations that find neighbourhoods or sort objects topologically. /// -public protocol ObjectGraph { - associatedtype Node: ObjectSnapshot - associatedtype Edge: EdgeProtocol - - /// List of indices of all nodes - var nodeIDs: [ObjectID] { get } - - /// List of indices of all edges - var edgeIDs: [ObjectID] { get } +public protocol GraphProtocol { + associatedtype Node: Identifiable + associatedtype Edge: EdgeProtocol where Edge.NodeID == Node.ID /// All nodes of the graph var nodes: [Node] { get } @@ -62,23 +34,23 @@ public protocol ObjectGraph { /// Get a node by ID. /// - func node(_ index: ObjectID) -> Node + func node(_ index: Node.ID) -> Node /// Get an edge by ID. /// - func edge(_ index: ObjectID) -> Edge + func edge(_ index: Edge.ID) -> Edge /// Check whether the graph contains a node object with given ID. /// /// - Returns: `true` if the graph contains the node. /// - func contains(node: ObjectID) -> Bool + func contains(node: Node.ID) -> Bool /// Check whether the graph contains an edge and whether the node is valid. /// /// - Returns: `true` if the graph contains the edge. /// - func contains(edge: ObjectID) -> Bool + func contains(edge: Edge.ID) -> Bool /// Get a list of outgoing edges from a node. /// @@ -88,14 +60,14 @@ public protocol ObjectGraph { /// /// - Returns: List of edges. /// - /// - Complexity: O(n) for the default implementation – all edges are traversed. + /// - Complexity: O(n). All edges are traversed in the default implementation. /// /// - Note: If you want to get both outgoing and incoming edges of a node /// then use ``neighbours(_:)``. Using ``outgoing(_:)`` + ``incoming(_:)`` might /// result in duplicates for edges that are loops to and from the same /// node. /// - func outgoing(_ origin: ObjectID) -> [Edge] + func outgoing(_ origin: Node.ID) -> [Edge] /// Get a list of edges incoming to a node. /// @@ -105,84 +77,97 @@ public protocol ObjectGraph { /// /// - Returns: List of edges. /// - /// - Complexity: O(n). All edges are traversed. + /// - Complexity: O(n). All edges are traversed in the default implementation. /// /// - Note: If you want to get both outgoing and incoming edges of a node /// then use ``neighbours(_:)``. Using ``outgoing(_:)`` + ``incoming(_:)`` might /// result in duplicates for edges that are loops to and from the same /// node. /// - func incoming(_ target: ObjectID) -> [Edge] - - /// Get a list of edges that are related to the neighbours of the node. That - /// is, list of edges where the node is either an origin or a target. - /// - /// - Returns: List of edges. - /// - /// - Complexity: O(n). All edges are traversed. - /// - func neighbours(_ node: ObjectID) -> [Edge] + func incoming(_ target: Node.ID) -> [Edge] /// Get a neighbourhood of a node where the edges match the neighbourhood /// selector `selector`. /// - func hood(_ nodeID: ObjectID, selector: NeighborhoodSelector) -> Neighborhood + func hood(_ nodeID: Node.ID, + direction: EdgeDirection, + where edgeMatch: (Edge) -> Bool) -> Neighborhood } -extension ObjectGraph { - public var nodeIDs: [ObjectID] { - nodes.map { $0.id } - } - - public var edgeIDs: [ObjectID] { - edges.map { $0.id } +extension GraphProtocol { + public func contains(node: Node.ID) -> Bool { + return nodes.contains { $0.id == node } } - - public func contains(node: ObjectID) -> Bool { - return nodeIDs.contains { $0 == node } - } - - public func contains(edge: ObjectID) -> Bool { - return edgeIDs.contains { $0 == edge } + + public func contains(edge: Edge.ID) -> Bool { + return edges.contains { $0.id == edge } } /// Get a node by ID. /// /// If id is `nil` then returns nil. /// - public func node(_ oid: ObjectID) -> Node { + public func node(_ oid: Node.ID) -> Node { guard let first: Node = nodes.first(where: { $0.id == oid }) else { fatalError("Missing node") } return first } - + /// Get an edge by ID. /// /// If id is `nil` then returns nil. /// - public func edge(_ oid: ObjectID) -> Edge { + public func edge(_ oid: Edge.ID) -> Edge { guard let first:Edge = edges.first(where: { $0.id == oid }) else { fatalError("Missing edge") } return first } - - public func outgoing(_ origin: ObjectID) -> [Edge] { + + public func outgoing(_ origin: Node.ID) -> [Edge] { return self.edges.filter { $0.origin == origin } } - public func incoming(_ target: ObjectID) -> [Edge] { + public func incoming(_ target: Node.ID) -> [Edge] { return self.edges.filter { $0.target == target } } - public func neighbours(_ node: ObjectID) -> [Edge] { - let result: [Edge] - - result = self.edges.filter { - $0.target == node || $0.origin == node + public func hood(_ nodeID: Node.ID, + direction: EdgeDirection, + where edgeMatch: (Edge) -> Bool) -> Neighborhood { + let edges: [Edge] + switch direction { + case .incoming: edges = incoming(nodeID) + case .outgoing: edges = outgoing(nodeID) } + let filtered: [Edge] = edges.filter { + edgeMatch($0) + } + + return Neighborhood(graph: self, + nodeID: nodeID, + direction: direction, + edges: filtered) + } +} - return result +/// Collection of nodes connected by edges. +/// +public struct Graph: GraphProtocol +where E.NodeID == N.ID { + public typealias Node = N + public typealias Edge = E + + /// List of nodes. + public var nodes: [N] = [] + + /// List of edges. + public var edges: [E] = [] + + /// Create a new graph with given list of nodes and edges. + public init(nodes: [Node], edges: [Edge]) { + self.nodes = nodes + self.edges = edges } } diff --git a/Sources/PoieticCore/Graph/Neighborhood.swift b/Sources/PoieticCore/Graph/Neighborhood.swift index 4dc4fb4..e9d1a57 100644 --- a/Sources/PoieticCore/Graph/Neighborhood.swift +++ b/Sources/PoieticCore/Graph/Neighborhood.swift @@ -25,41 +25,21 @@ public enum EdgeDirection: Sendable { } } -// FIXME: [CLEANUP] Remove this and follow the TODO below. -// TODO: Do we still need this? Can't we just have predicate + direction in the hood? -public final class NeighborhoodSelector: Sendable { - public let direction: EdgeDirection - public let predicate: Predicate - - public init(predicate: Predicate, - direction: EdgeDirection) { - self.predicate = predicate - self.direction = direction - } -} - -// TODO: [EXPERIMENTAL] The following is experimental -// TODO: Rethink Neighbourhoods. They are useful, but not well implemented -// TODO: Split this into Bound and Unbound -// TODO: Document complexity O(n) - all edges are traversed -// TODO: Make it a view. - -// NOTE: There used to be more evolved neighbourhood-like collection of -// objects in the past. This is what remained. /// Neighbourhood is a subgraph centred on a node with edges adjacent to /// that node. /// /// Neighbourhoods are created using ``Graph/hood(_:selector:)``. /// -public class Neighborhood { +public class Neighborhood { + public typealias GraphType = G /// Graph the neighbourhood is contained within. /// - public let graph: G + public let graph: GraphType /// ID of a node the neighbourhood adjacent to. /// - public let nodeID: ObjectID + public let nodeID: GraphType.Node.ID /// Direction of the edges to be followed from the main node. /// @@ -69,7 +49,7 @@ public class Neighborhood { /// public let edges: [G.Edge] - public init(graph: G, nodeID: ObjectID, direction: EdgeDirection, edges: [G.Edge]) { + public init(graph: G, nodeID: GraphType.Node.ID, direction: EdgeDirection, edges: [G.Edge]) { self.graph = graph self.nodeID = nodeID self.direction = direction @@ -78,7 +58,7 @@ public class Neighborhood { public var nodes: [G.Node] { edges.map { edge in - let endpointID: ObjectID + let endpointID: GraphType.Node.ID switch direction { case .incoming: endpointID = edge.origin case .outgoing: endpointID = edge.target