From f911604070bc9c68691439537e209210072f17c9 Mon Sep 17 00:00:00 2001 From: BrianLusina <12752833+BrianLusina@users.noreply.github.com> Date: Tue, 5 Dec 2023 10:08:51 +0300 Subject: [PATCH 1/3] feat(vertex): add adjacent vertex --- datastructures/graphs/vertex.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/datastructures/graphs/vertex.py b/datastructures/graphs/vertex.py index 1c580bf2..aea9d564 100644 --- a/datastructures/graphs/vertex.py +++ b/datastructures/graphs/vertex.py @@ -17,6 +17,7 @@ def __init__(self, data: T, incoming_edges: Set[Edge], outgoing_edges: Set[Edge] self.incoming_edges = incoming_edges self.outgoing_edges = outgoing_edges self.edges = self.incoming_edges.union(self.outgoing_edges) + self.adjacent_vertices: Dict[str, 'Vertex'] = {} self.properties = properties def __str__(self): @@ -109,6 +110,9 @@ def add_adjacent_vertex(self, other: 'Vertex'): Args: other (Vertex): Vertex to add as a neighbor """ + if not self.adjacent_vertices.get(other.id): + self.adjacent_vertices[other.id] = other + other.add_adjacent_vertex(self) def __eq__(self, other: 'Vertex') -> bool: return self.id == other.id From 53aa2f43f0807a862b753a60df510f66f178d4e5 Mon Sep 17 00:00:00 2001 From: Lusina <12752833+BrianLusina@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:15:46 +0300 Subject: [PATCH 2/3] refactor(datastructures, graphs): refactor edge implementation --- datastructures/graphs/__init__.py | 242 +----------------- datastructures/graphs/edge.py | 54 ---- datastructures/graphs/edge/__init__.py | 15 ++ datastructures/graphs/edge/edge.py | 36 +++ datastructures/graphs/edge/edge_directed.py | 28 ++ datastructures/graphs/edge/edge_hyper.py | 26 ++ datastructures/graphs/edge/edge_self.py | 26 ++ datastructures/graphs/edge/edge_type.py | 10 + datastructures/graphs/edge/edge_undirected.py | 27 ++ datastructures/graphs/graph.py | 230 +++++++++++++++++ datastructures/graphs/test_vertex.py | 16 ++ datastructures/graphs/vertex.py | 66 ++--- 12 files changed, 461 insertions(+), 315 deletions(-) delete mode 100644 datastructures/graphs/edge.py create mode 100644 datastructures/graphs/edge/__init__.py create mode 100644 datastructures/graphs/edge/edge.py create mode 100644 datastructures/graphs/edge/edge_directed.py create mode 100644 datastructures/graphs/edge/edge_hyper.py create mode 100644 datastructures/graphs/edge/edge_self.py create mode 100644 datastructures/graphs/edge/edge_type.py create mode 100644 datastructures/graphs/edge/edge_undirected.py create mode 100644 datastructures/graphs/graph.py create mode 100644 datastructures/graphs/test_vertex.py diff --git a/datastructures/graphs/__init__.py b/datastructures/graphs/__init__.py index 21360257..1e928568 100755 --- a/datastructures/graphs/__init__.py +++ b/datastructures/graphs/__init__.py @@ -1,230 +1,14 @@ -from abc import ABC, abstractmethod -from collections import defaultdict -from pprint import PrettyPrinter -from typing import List, Set, Union, Generic, TypeVar -from datastructures.stacks import Stack from .vertex import Vertex -from .edge import Edge - -T = TypeVar("T") - - -class Graph(ABC, Generic[T]): - """ - Represents a Graph Data structure - """ - - def __init__(self, edge_list: List[Edge] = None): - if edge_list is None: - edge_list = [] - self.edge_list = edge_list - self.adjacency_list = defaultdict(List[Vertex]) - self.__construct_adjacency_list() - self.nodes = [] - self.node_count = len(self.nodes) - - def add(self, node_one: Vertex, node_two: Vertex): - """ - Adds a connection between node_one and node_two - """ - node_one.neighbours.append(node_two) - node_two.neighbours.append(node_one) - edge = Edge(source=node_one, destination=node_two) - self.edge_list.append(edge) - self.adjacency_list[node_one].append(node_two) - self.adjacency_list[node_two].append(node_one) - self.nodes.append(node_one) - self.nodes.append(node_two) - - def __construct_adjacency_list(self): - """ - Construct adjacency list - """ - for edge in self.edge_list: - self.adjacency_list[edge.source].append(edge.destination) - - @abstractmethod - def bfs_from_root_to_target(self, root: Vertex, target: Vertex) -> Set[Vertex]: - """ - Given the root node to traverse and a target node, returns the BFS result of this Graph from the root node to - the target node - """ - raise NotImplementedError("Not yet implemented") - - @abstractmethod - def bfs_from_node(self, source: Vertex) -> Set[Vertex]: - """ - Given the source to traverse, returns the BFS result of this Graph from the source node - """ - raise NotImplementedError("Not yet implemented") - - def topological_sorted_order(self) -> List[Vertex]: - """ - Returns the topological sorted order of the Graph - """ - # These static variables are used to perform DFS recursion - # white nodes depict nodes that have not been visited yet - # gray nodes depict ongoing recursion - # black nodes depict recursion is complete - # An edge leading to a BLACK node is not a "cycle" - white = 1 - gray = 2 - black = 3 - - # Nothing to do here - if self.node_count == 0: - return [] - - is_possible = True - stack = Stack() - - # By default all nodes are WHITE - visited_nodes = {node: white for node in range(self.node_count)} - - def dfs(node: Vertex): - nonlocal is_possible - - # Don't recurse further if we found a cycle already - if not is_possible: - return - - # start recursion - visited_nodes[node] = gray - - # Traverse on neighbouring nodes/vertices - if node in self.adjacency_list: - for neighbour in self.adjacency_list[node]: - if visited_nodes[neighbour] == white: - dfs(node) - elif visited_nodes[node] == gray: - # An Edge to a Gray vertex/node represents a cycle - is_possible = False - - # Recursion ends. We mark if as BLACK - visited_nodes[node] = black - stack.push(node) - - for node in self.nodes: - # if the node is unprocessed, then call DFS on it - if visited_nodes[node] == white: - dfs(node) - - return list(stack.stack) if is_possible else [] - - def print(self): - pretty_print = PrettyPrinter() - pretty_print.pprint(self.adjacency_list) - - def remove(self, node: Vertex) -> None: - """ - Removes all references to a node - :param node - """ - for _, cxns in self.adjacency_list.items(): - try: - cxns.remove(node) - except KeyError: - pass - - try: - del self.adjacency_list[node] - except KeyError: - pass - - def is_connected(self, node_one: Vertex, node_two: Vertex) -> bool: - return ( - node_one in self.adjacency_list - and node_two in self.adjacency_list[node_two] - ) - - def find_path( - self, node_one: Vertex, node_two: Vertex, path=None - ) -> Union[List, None]: - """ - Find any path between node_one and node_two. May not be the shortest path - :param node_one - :param node_two - :param path - """ - - if path is None: - path = [] - path = [path] + [node_one] - - if node_one.data == node_two.data: - return path - - if node_one.data not in self.adjacency_list: - return None - - for node in self.adjacency_list[node_one]: - if node.data not in path: - new_path = self.find_path(node, node_two, path) - - if new_path: - return new_path - - return None - - def find_all_paths( - self, node_one: Vertex, node_two: Vertex, path: List = None - ) -> list: - """ - Finds all paths between node_one and node_two, where node_one is the start & node_two is the end - :param node_one Graph Node - :param node_two Graph Node - :param path - """ - if path is None: - path = [] - path = path + [node_one] - - if node_one.data == node_two.data: - return [path] - - if node_one.data not in self.adjacency_list: - return [] - - paths = [] - - for node in self.adjacency_list[node_one.data]: - if node not in path: - newpaths = self.find_all_paths(Vertex(node), node_two, path) - for newpath in newpaths: - paths.append(newpath) - - return paths - - def find_shortest_path( - self, node_one: Vertex, node_two: Vertex, path: List = None - ) -> Union[List, None]: - """ - Finds the shortest path between 2 nodes in the graph - """ - if path is None: - path = [] - - path = path + [node_one] - - if node_one.data == node_two.data: - return path - - if node_one.data not in self.adjacency_list: - return None - - shortest = None - - for node in self.adjacency_list[node_one]: - if node.data not in path: - newpath = self.find_shortest_path(node, node_two, path) - if newpath: - if not shortest or len(newpath) < len(shortest): - shortest = newpath - - return shortest - - def __str__(self): - """ - Return string representation of this Graph - """ - return f"Graph: {self.adjacency_list}" +from .edge import Edge, EdgeType, DirectedEdge, UndirectedEdge, SelfEdge, HyperEdge +from .graph import Graph + +__all__ = [ + "Edge", + "EdgeType", + "Vertex", + "Graph", + "UndirectedEdge", + "DirectedEdge", + "SelfEdge", + "HyperEdge", +] diff --git a/datastructures/graphs/edge.py b/datastructures/graphs/edge.py deleted file mode 100644 index 405062c8..00000000 --- a/datastructures/graphs/edge.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import AnyStr, Optional, Union, Dict, Any -from enum import Enum, unique -from uuid import uuid4 -from .vertex import Vertex - - -@unique -class EdgeType(Enum): - UNDIRECTED = 1 - DIRECTED = 2 - SELF_DIRECTED = 3 - SELF_UNDIRECTED = 4 - HYPER_DIRECTED = 5 - HYPER_UNDIRECTED = 6 - - -class Edge: - """ - Edge representation of an Edge in a Graph - """ - - def __init__( - self, - source: Vertex, - destination: Vertex, - weight: Optional[Union[int, float]] = None, - edge_type: EdgeType = EdgeType.UNDIRECTED, - relationship_type: Optional[AnyStr] = None, - properties: Optional[Dict[str, Any]] = None, - identifier: AnyStr = uuid4(), - ): - self.id = identifier - self.source = source - self.destination = destination - self.weight = weight - self.type = edge_type - self.properties = properties - self.relationship_type = relationship_type - - self.__validate_edge() - - def __validate_edge(self): - if self.type == EdgeType.SELF_DIRECTED or self.type == EdgeType.SELF_UNDIRECTED: - if self.destination.id != self.source.id: - raise ValueError( - f"Edge denoted as {self.type} but source node {self.source} & " - f"destination node {self.destination} are not the same" - ) - - def __str__(self): - return ( - f"Id: {self.id}, Source: {self.source}, Destination: {self.destination}, Weight: {self.weight}, " - f"Properties: {self.properties}, RelationshipType: {self.relationship_type}" - ) diff --git a/datastructures/graphs/edge/__init__.py b/datastructures/graphs/edge/__init__.py new file mode 100644 index 00000000..17257a08 --- /dev/null +++ b/datastructures/graphs/edge/__init__.py @@ -0,0 +1,15 @@ +from .edge import Edge +from .edge_type import EdgeType +from .edge_undirected import UndirectedEdge +from .edge_directed import DirectedEdge +from .edge_self import SelfEdge +from .edge_hyper import HyperEdge + +__all__ = [ + "Edge", + "EdgeType", + "UndirectedEdge", + "DirectedEdge", + "SelfEdge", + "HyperEdge", +] diff --git a/datastructures/graphs/edge/edge.py b/datastructures/graphs/edge/edge.py new file mode 100644 index 00000000..cb754e50 --- /dev/null +++ b/datastructures/graphs/edge/edge.py @@ -0,0 +1,36 @@ +from abc import ABC, abstractmethod +from typing import AnyStr, Union +from .edge_type import EdgeType +from typing import Any, Dict, Optional, Generic, TypeVar, List +from uuid import uuid4 + +T = TypeVar("T") + +class Edge(ABC, Generic[T]): + """ + Edge representation of an abstract Edge in a Graph + """ + + def __init__( + self, + weight: Optional[Union[int, float]] = None, + properties: Optional[Dict[str, Any]] = None, + identifier: AnyStr = uuid4(), + ): + self.id = identifier + self.weight = weight + self.properties = properties + + def __str__(self): + return f"Id: {self.id}, Weight: {self.weight}, Properties: {self.properties}" + + @abstractmethod + def edge_type(self) -> EdgeType: + raise NotImplementedError("Not implemented") + + def is_unweighted(self) -> bool: + return self.weight is None + + @abstractmethod + def vertices(self) -> List[Any]: + raise NotImplementedError("Not implemented") diff --git a/datastructures/graphs/edge/edge_directed.py b/datastructures/graphs/edge/edge_directed.py new file mode 100644 index 00000000..b62b4567 --- /dev/null +++ b/datastructures/graphs/edge/edge_directed.py @@ -0,0 +1,28 @@ +from typing import AnyStr, Union, Dict, Optional, Generic, TypeVar, List, Any +from uuid import uuid4 +from .edge_type import EdgeType +from .edge import Edge + +T = TypeVar("T") + +class DirectedEdge(Edge, Generic[T]): + """ + Directed Edge representation of a directed Edge in a Graph where the edge connects two vertices which has a source + vertex and a destination vertex. + """ + + def __init__(self, source: Any, destination: Any, weight: Optional[Union[int, float]] = None, + properties: Optional[Dict[str, Any]] = None, + identifier: AnyStr = uuid4()): + super().__init__(weight, properties, identifier) + self.source = source + self.destination = destination + + def __str__(self): + return f"{super().__str__()}, Source: {self.source}, Destination: {self.destination}" + + def edge_type(self) -> EdgeType: + return EdgeType.DIRECTED + + def vertices(self) -> List[Any]: + return [self.source, self.destination] diff --git a/datastructures/graphs/edge/edge_hyper.py b/datastructures/graphs/edge/edge_hyper.py new file mode 100644 index 00000000..5f863e33 --- /dev/null +++ b/datastructures/graphs/edge/edge_hyper.py @@ -0,0 +1,26 @@ +from typing import AnyStr, Union, Dict, Optional, Generic, TypeVar, List, Any +from uuid import uuid4 +from .edge_type import EdgeType +from .edge import Edge + +T = TypeVar("T") + +class HyperEdge(Edge, Generic[T]): + """ + HyperEdge representation of a hyper-edge in a Graph where the edge connects to the multiple vertices + """ + + def __init__(self, nodes: List[Any], weight: Optional[Union[int, float]] = None, + properties: Optional[Dict[str, Any]] = None, + identifier: AnyStr = uuid4()): + super().__init__(weight, properties, identifier) + self.nodes = nodes + + def __str__(self): + return f"{super().__str__()}, Nodes: {self.nodes}" + + def edge_type(self) -> EdgeType: + return EdgeType.SELF + + def vertices(self) -> List[Any]: + return self.nodes diff --git a/datastructures/graphs/edge/edge_self.py b/datastructures/graphs/edge/edge_self.py new file mode 100644 index 00000000..4ea1cd99 --- /dev/null +++ b/datastructures/graphs/edge/edge_self.py @@ -0,0 +1,26 @@ +from typing import AnyStr, Union, Dict, Optional, Generic, TypeVar, List, Any +from uuid import uuid4 +from .edge_type import EdgeType +from .edge import Edge + +T = TypeVar("T") + +class SelfEdge(Edge, Generic[T]): + """ + Self-Edge representation of a self-edge in a Graph where the edge connects to the same vertex + """ + + def __init__(self, node: Any, weight: Optional[Union[int, float]] = None, + properties: Optional[Dict[str, Any]] = None, + identifier: AnyStr = uuid4()): + super().__init__(weight, properties, identifier) + self.node_one = node + + def __str__(self): + return f"{super().__str__()}, Node: {self.node_one}" + + def edge_type(self) -> EdgeType: + return EdgeType.SELF + + def vertices(self) -> List[Any]: + return [self.node_one] diff --git a/datastructures/graphs/edge/edge_type.py b/datastructures/graphs/edge/edge_type.py new file mode 100644 index 00000000..436fb065 --- /dev/null +++ b/datastructures/graphs/edge/edge_type.py @@ -0,0 +1,10 @@ +from enum import Enum, unique + + +@unique +class EdgeType(Enum): + UNDIRECTED = 1 + DIRECTED = 2 + SELF = 3 + HYPER_DIRECTED = 4 + HYPER_UNDIRECTED = 5 diff --git a/datastructures/graphs/edge/edge_undirected.py b/datastructures/graphs/edge/edge_undirected.py new file mode 100644 index 00000000..ac856973 --- /dev/null +++ b/datastructures/graphs/edge/edge_undirected.py @@ -0,0 +1,27 @@ +from typing import AnyStr, Union, Dict, Optional, Generic, TypeVar, List, Any +from uuid import uuid4 +from .edge_type import EdgeType +from .edge import Edge + +T = TypeVar("T") + +class UndirectedEdge(Edge, Generic[T]): + """ + Undirected Edge representation of an undirected Edge in a Graph where the edge connects two vertices. + """ + + def __init__(self, node_one: Any, node_two: Any, weight: Optional[Union[int, float]] = None, + properties: Optional[Dict[str, Any]] = None, + identifier: AnyStr = uuid4()): + super().__init__(weight, properties, identifier) + self.node_one = node_one + self.node_two = node_two + + def __str__(self): + return f"{super().__str__()}, NodeOne: {self.node_one}, NodeTwo: {self.node_two}" + + def edge_type(self) -> EdgeType: + return EdgeType.Undirected + + def vertices(self) -> List[Any]: + return [self.node_one, self.node_two] diff --git a/datastructures/graphs/graph.py b/datastructures/graphs/graph.py new file mode 100644 index 00000000..bbc7a4aa --- /dev/null +++ b/datastructures/graphs/graph.py @@ -0,0 +1,230 @@ +from abc import ABC, abstractmethod +from collections import defaultdict +from pprint import PrettyPrinter +from typing import List, Set, Union, Generic, TypeVar +from datastructures.stacks import Stack +from .vertex import Vertex +from .edge import Edge + +T = TypeVar("T") + + +class Graph(ABC, Generic[T]): + """ + Represents a Graph Data structure + """ + + def __init__(self, edge_list: List[Edge] = None): + if edge_list is None: + edge_list = [] + self.edge_list = edge_list + self.adjacency_list = defaultdict(List[Vertex]) + self.__construct_adjacency_list() + self.nodes = [] + self.node_count = len(self.nodes) + + def add(self, source_node: Vertex, destination_node: Vertex): + """ + Adds a connection between node_one and node_two + """ + source_node.neighbours.append(destination_node) + destination_node.neighbours.append(source_node) + edge = Edge(node_one=source_node, node_two=destination_node) + self.edge_list.append(edge) + self.adjacency_list[source_node].append(destination_node) + self.adjacency_list[destination_node].append(source_node) + self.nodes.append(source_node) + self.nodes.append(destination_node) + + def __construct_adjacency_list(self): + """ + Construct adjacency list + """ + for edge in self.edge_list: + self.adjacency_list[edge.node_one].append(edge.node_two) + + @abstractmethod + def bfs_from_root_to_target(self, root: Vertex, target: Vertex) -> Set[Vertex]: + """ + Given the root node to traverse and a target node, returns the BFS result of this Graph from the root node to + the target node + """ + raise NotImplementedError("Not yet implemented") + + @abstractmethod + def bfs_from_node(self, source: Vertex) -> Set[Vertex]: + """ + Given the source to traverse, returns the BFS result of this Graph from the source node + """ + raise NotImplementedError("Not yet implemented") + + def topological_sorted_order(self) -> List[Vertex]: + """ + Returns the topological sorted order of the Graph + """ + # These static variables are used to perform DFS recursion + # white nodes depict nodes that have not been visited yet + # gray nodes depict ongoing recursion + # black nodes depict recursion is complete + # An edge leading to a BLACK node is not a "cycle" + white = 1 + gray = 2 + black = 3 + + # Nothing to do here + if self.node_count == 0: + return [] + + is_possible = True + stack = Stack() + + # By default all nodes are WHITE + visited_nodes = {node: white for node in range(self.node_count)} + + def dfs(node: Vertex): + nonlocal is_possible + + # Don't recurse further if we found a cycle already + if not is_possible: + return + + # start recursion + visited_nodes[node] = gray + + # Traverse on neighbouring nodes/vertices + if node in self.adjacency_list: + for neighbour in self.adjacency_list[node]: + if visited_nodes[neighbour] == white: + dfs(node) + elif visited_nodes[node] == gray: + # An Edge to a Gray vertex/node represents a cycle + is_possible = False + + # Recursion ends. We mark if as BLACK + visited_nodes[node] = black + stack.push(node) + + for node in self.nodes: + # if the node is unprocessed, then call DFS on it + if visited_nodes[node] == white: + dfs(node) + + return list(stack.stack) if is_possible else [] + + def print(self): + pretty_print = PrettyPrinter() + pretty_print.pprint(self.adjacency_list) + + def remove(self, node: Vertex) -> None: + """ + Removes all references to a node + :param node + """ + for _, cxns in self.adjacency_list.items(): + try: + cxns.remove(node) + except KeyError: + pass + + try: + del self.adjacency_list[node] + except KeyError: + pass + + def is_connected(self, node_one: Vertex, node_two: Vertex) -> bool: + return ( + node_one in self.adjacency_list + and node_two in self.adjacency_list[node_two] + ) + + def find_path( + self, node_one: Vertex, node_two: Vertex, path=None + ) -> Union[List, None]: + """ + Find any path between node_one and node_two. May not be the shortest path + :param node_one + :param node_two + :param path + """ + + if path is None: + path = [] + path = [path] + [node_one] + + if node_one.data == node_two.data: + return path + + if node_one.data not in self.adjacency_list: + return None + + for node in self.adjacency_list[node_one]: + if node.data not in path: + new_path = self.find_path(node, node_two, path) + + if new_path: + return new_path + + return None + + def find_all_paths( + self, node_one: Vertex, node_two: Vertex, path: List = None + ) -> list: + """ + Finds all paths between node_one and node_two, where node_one is the start & node_two is the end + :param node_one Graph Node + :param node_two Graph Node + :param path + """ + if path is None: + path = [] + path = path + [node_one] + + if node_one.data == node_two.data: + return [path] + + if node_one.data not in self.adjacency_list: + return [] + + paths = [] + + for node in self.adjacency_list[node_one.data]: + if node not in path: + newpaths = self.find_all_paths(Vertex(node), node_two, path) + for newpath in newpaths: + paths.append(newpath) + + return paths + + def find_shortest_path( + self, node_one: Vertex, node_two: Vertex, path: List = None + ) -> Union[List, None]: + """ + Finds the shortest path between 2 nodes in the graph + """ + if path is None: + path = [] + + path = path + [node_one] + + if node_one.data == node_two.data: + return path + + if node_one.data not in self.adjacency_list: + return None + + shortest = None + + for node in self.adjacency_list[node_one]: + if node.data not in path: + newpath = self.find_shortest_path(node, node_two, path) + if newpath: + if not shortest or len(newpath) < len(shortest): + shortest = newpath + + return shortest + + def __str__(self): + """ + Return string representation of this Graph + """ + return f"Graph: {self.adjacency_list}" diff --git a/datastructures/graphs/test_vertex.py b/datastructures/graphs/test_vertex.py new file mode 100644 index 00000000..d22ef433 --- /dev/null +++ b/datastructures/graphs/test_vertex.py @@ -0,0 +1,16 @@ +import unittest + +from datastructures.graphs import Vertex, UndirectedEdge + + +class VertexTestCases(unittest.TestCase): + def test_1(self): + node_one = Vertex(data=1) + node_two = Vertex(data=2) + edge = UndirectedEdge(node_one=node_one, node_two=node_two) + + self.assertEqual(True, False) # add assertion here + + +if __name__ == '__main__': + unittest.main() diff --git a/datastructures/graphs/vertex.py b/datastructures/graphs/vertex.py index 35878a1e..455929a3 100644 --- a/datastructures/graphs/vertex.py +++ b/datastructures/graphs/vertex.py @@ -13,11 +13,15 @@ class Vertex(Generic[T]): def __init__( self, data: T, - incoming_edges: Set[Edge], - outgoing_edges: Set[Edge], - properties: Optional[Dict[str, Any]] = None, identifier: Any = uuid4(), + properties: Optional[Dict[str, Any]] = None, + incoming_edges: Optional[Set[Edge]] = None, + outgoing_edges: Optional[Set[Edge]] = None, ): + if outgoing_edges is None: + outgoing_edges = set() + if incoming_edges is None: + incoming_edges = set() self.id = identifier self.data = data self.incoming_edges = incoming_edges @@ -32,24 +36,35 @@ def __str__(self): f"Degree: {self.degree}" ) + def __eq__(self, other: "Vertex") -> bool: + return self.id == other.id + + def add_adjacent_vertex(self, other: "Vertex") -> None: + """Adds an adjacent vertex to the list of neighbors. Note that this is useful in a graph as the graph will be + able the call this method on this vertex and the same method on the other vertex showing undirected relationship. + + Args: + other (Vertex): Vertex to add as a neighbor + """ + # should not be able to add self as an adjacent vertex + if other is self or other.id == self.id: + return + + # only add adjacent vertex if not already present. + if not self.adjacent_vertices.get(other.id): + self.adjacent_vertices[other.id] = other + other.add_adjacent_vertex(self) + @property def neighbours(self) -> List["Vertex"]: - """Returns a list of all the direct neighbours of this vertex + """Returns a list of all the direct neighbors of this vertex Returns: - List: list of vertices that are direct neighbours or this vertex + List: list of vertices that are direct neighbors or this vertex """ nodes = [] - for edge in self.incoming_edges: - node = edge.source - if node.id != self.id: - nodes.append(node) - nodes.append(node) - - for edge in self.outgoing_edges: - node = edge.destination - if node.id != self.id: - nodes.append(node) + for vertex in self.adjacent_vertices.values(): + nodes.append(vertex) return nodes @@ -62,10 +77,10 @@ def degree(self) -> int: """ degrees = 0 - if len(self.incoming_edge) == 0 or len(self.outgoing_edges) == 0: + if len(self.incoming_edges) == 0 or len(self.outgoing_edges) == 0: return degrees - seen_edges: Set = {} + seen_edges: Set = set() for edge in self.edges: if edge not in seen_edges: @@ -88,7 +103,7 @@ def in_degree(self) -> int: return in_degrees for edge in self.edges: - if edge.type == EdgeType.DIRECTED and edge.destination == self: + if edge.type == EdgeType.DIRECTED and edge.node_two == self: in_degrees += 1 return in_degrees @@ -106,21 +121,8 @@ def out_degree(self) -> int: return out_degrees for edge in self.edges: - if edge.type == EdgeType.DIRECTED and edge.source == self: + if edge.type == EdgeType.DIRECTED and edge.node_one == self: out_degrees += 1 return out_degrees - def add_adjacent_vertex(self, other: "Vertex"): - """Adds an adjacent vertex to the list of neighbors. Note that this is useful in a graph as the graph will be - able the call this method on this vertex & the same method on the other vertex showing undirected relationship. - - Args: - other (Vertex): Vertex to add as a neighbor - """ - if not self.adjacent_vertices.get(other.id): - self.adjacent_vertices[other.id] = other - other.add_adjacent_vertex(self) - - def __eq__(self, other: "Vertex") -> bool: - return self.id == other.id From 69d657a024736fc9e4f968a29a6bad026b5a8d6f Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Fri, 24 Oct 2025 11:16:11 +0000 Subject: [PATCH 3/3] updating DIRECTORY.md --- DIRECTORY.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index 032c1824..f2b24424 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -195,7 +195,15 @@ * [Default Dicts](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/dicts/default_dicts.py) * [Ordered Dict](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/dicts/ordered_dict.py) * Graphs - * [Edge](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/edge.py) + * Edge + * [Edge](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/edge/edge.py) + * [Edge Directed](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/edge/edge_directed.py) + * [Edge Hyper](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/edge/edge_hyper.py) + * [Edge Self](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/edge/edge_self.py) + * [Edge Type](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/edge/edge_type.py) + * [Edge Undirected](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/edge/edge_undirected.py) + * [Graph](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/graph.py) + * [Test Vertex](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/test_vertex.py) * [Vertex](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/graphs/vertex.py) * Hashmap * [Test Hashmap](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/hashmap/test_hashmap.py)