Skip to content

Conversation

@BrianLusina
Copy link
Owner

@BrianLusina BrianLusina commented Dec 5, 2023

Vertex & Graph implementations

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Documentation change?

Checklist:

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added specialized edge types: DirectedEdge, UndirectedEdge, SelfEdge, and HyperEdge for flexible graph representations
    • Introduced Graph class with pathfinding and topological sorting capabilities
  • Refactor

    • Reorganized graph and edge implementations into a modular package structure with centralized exports
    • Enhanced Vertex with adjacency tracking and equality comparison
  • Tests

    • Added vertex test suite

@BrianLusina BrianLusina added Algorithm Algorithm Problem Datastructures Datastructures Documentation Documentation Updates Graph Graph data structures and algorithms labels Dec 5, 2023
@BrianLusina BrianLusina self-assigned this Dec 5, 2023
@coderabbitai
Copy link

coderabbitai bot commented Oct 24, 2025

Walkthrough

This pull request refactors the graph data structures module by converting a monolithic edge implementation into a package-based architecture with an abstract base Edge class and concrete subclasses for different edge types. The module facade is updated to re-export from submodules, and a new Graph class implementing adjacency-list operations is introduced. The Vertex class is enhanced with additional properties and methods.

Changes

Cohort / File(s) Summary
Documentation Updates
DIRECTORY.md
Expanded Graphs section with organized edge type entries (Edge Directed, Edge Hyper, Edge Self, Edge Type, Edge Undirected) and added new Graph and Test Vertex entries.
Module Facade Restructuring
datastructures/graphs/__init__.py
Converted from monolithic implementation to facade pattern; now re-exports Edge, EdgeType, Vertex, Graph, and edge subclasses from respective submodules, with explicit all declaration.
Removed Legacy Edge Implementation
datastructures/graphs/edge.py
Removed original monolithic Edge and EdgeType classes, including validation logic and string representations.
New Edge Package Structure
datastructures/graphs/edge/__init__.py
New package initializer; centralizes imports and exports Edge, EdgeType, and all edge subclass types via all.
Edge Abstraction Layer
datastructures/graphs/edge/edge.py
New abstract generic base class Edge[T] with optional weight/properties, abstract methods edge_type() and vertices(), utility method is_unweighted(), and concise str().
Edge Type Enum
datastructures/graphs/edge/edge_type.py
New enum EdgeType with members UNDIRECTED, DIRECTED, SELF, HYPER_DIRECTED, HYPER_UNDIRECTED, decorated with @unique.
Concrete Edge Implementations
datastructures/graphs/edge/edge_directed.py, datastructures/graphs/edge/edge_undirected.py, datastructures/graphs/edge/edge_self.py, datastructures/graphs/edge/edge_hyper.py
Four new concrete edge classes extending abstract Edge; DirectedEdge stores source/destination, UndirectedEdge stores two nodes, SelfEdge stores single node, HyperEdge stores multiple nodes; each implements edge_type() and vertices().
Graph Implementation
datastructures/graphs/graph.py
New generic Graph class with adjacency-list representation; supports add(), remove(), topological_sorted_order(), path-finding methods (find_path(), find_all_paths(), find_shortest_path()), is_connected(), and graph inspection methods.
Vertex Enhancement
datastructures/graphs/vertex.py
Updated constructor parameter order (identifier moved first); added adjacent_vertices attribute, eq() method, and add_adjacent_vertex() method; adjusted edge handling logic.
Test Coverage
datastructures/graphs/test_vertex.py
New unit test file with VertexTestCases suite; includes placeholder test_1 method constructing Vertex and UndirectedEdge instances.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Graph
    participant Vertex
    participant Edge Subclass
    participant EdgeType

    Client->>Graph: add(source_node, destination_node)
    Graph->>Vertex: access source/destination properties
    Graph->>Edge Subclass: create edge (DirectedEdge, UndirectedEdge, etc.)
    Edge Subclass->>EdgeType: return edge_type()
    Graph->>Vertex: add_adjacent_vertex(other)
    Vertex->>Vertex: update adjacent_vertices
    Graph->>Graph: update adjacency_list and edge_list

    Client->>Graph: find_path(node_one, node_two)
    Graph->>Vertex: access adjacent_vertices
    Graph->>Graph: recursive traversal via adjacent_vertices
    Graph-->>Client: return path or None

    Client->>Graph: topological_sorted_order()
    Graph->>Graph: DFS with white/gray/black marking
    Graph->>Graph: cycle detection
    Graph-->>Client: return sorted order or empty
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The changes involve multiple interrelated files with new abstraction patterns, generic type parameters, and updated data structures. The refactoring from monolithic to package-based architecture with concrete subclasses requires careful verification of inheritance relationships and method contracts. The Graph and Vertex class modifications introduce significant behavioral changes and new public APIs. The logic density in path-finding and topological sort methods requires scrutiny. While changes follow a consistent pattern, the heterogeneous scope across edge types, graph operations, and vertex updates demands independent reasoning for several sections.

Poem

🐰 A graph stands tall with edges so neat,
Directed, undirected—a structured feat!
From monolithic one, we split the seams,
Into abstract dreams and subclass schemes,
Vertices now dance with paths to find,
A hop-skip topology blooms in mind! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description is missing the primary "### Describe your change:" section entirely, replacing it with only a subsection title "### Vertex & Graph implementations" without providing context about what was changed and why. Additionally, three checklist items from the template remain unchecked: "All functions have doctests", "All new algorithms have a URL in its comments", and "If this pull request resolves one or more open issues". While the author has completed eight of the eleven template items, the absence of the main descriptive section and these incomplete checklist items represent a significant gap from the required template structure. Add the "### Describe your change:" section to explain what modifications were made and the rationale behind them. Additionally, either complete or explicitly address the three unchecked items (doctests, reference URLs, and issue references) by either checking them if applicable or noting why they do not apply to this particular PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 34.09% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "Graphs | Vertex & Graph implementations" describes real components being added in this changeset, specifically referring to Vertex and Graph classes that are substantively modified and/or introduced. However, the changeset encompasses significantly more work than the title suggests, including a major refactoring of the Edge system (moving from a monolithic implementation to a modular package structure with specialized edge types like DirectedEdge, UndirectedEdge, SelfEdge, and HyperEdge). The title represents a partial but valid aspect of the overall changes without being misleading about what is present.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/graphs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

Warnings
⚠️ ❗ Big PR

: Pull Request size seems relatively large. If Pull Request contains multiple changes, split each into separate PR will helps faster, easier review.

Generated by 🚫 dangerJS against 69d657a

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
datastructures/graphs/vertex.py (2)

72-91: Degree calculation is incorrect.

  • Early return triggers when either incoming or outgoing is empty; should be when both are empty.
  • Use Edge.is_unweighted() per API; don’t treat weight 0 as unweighted.
-        degrees = 0
-
-        if len(self.incoming_edges) == 0 or len(self.outgoing_edges) == 0:
-            return degrees
-
-        seen_edges: Set = set()
-
-        for edge in self.edges:
-            if edge not in seen_edges:
-                seen_edges.add(edge)
-                if not edge.weight:
-                    degrees += 1
-
-        return degrees
+        if not self.edges:
+            return 0
+        return sum(1 for edge in self.edges if edge.is_unweighted())

101-109: Directed-degree checks reference non-existent attributes.

Use DirectedEdge and its source/destination fields (or edge.edge_type()). Current code uses edge.type and node_one/node_two, which don’t exist for DirectedEdge.

-        if len(self.edges) == 0:
+        if not self.edges:
             return in_degrees
-
-        for edge in self.edges:
-            if edge.type == EdgeType.DIRECTED and edge.node_two == self:
-                in_degrees += 1
+        for edge in self.edges:
+            if isinstance(edge, DirectedEdge) and edge.destination == self:
+                in_degrees += 1
@@
-        if len(self.edges) == 0:
+        if not self.edges:
             return out_degrees
-
-        for edge in self.edges:
-            if edge.type == EdgeType.DIRECTED and edge.node_one == self:
-                out_degrees += 1
+        for edge in self.edges:
+            if isinstance(edge, DirectedEdge) and edge.source == self:
+                out_degrees += 1

Also applies to: 123-127

🧹 Nitpick comments (7)
datastructures/graphs/vertex.py (1)

58-69: Minor: simplify neighbours.

No behavior change.

-        nodes = []
-        for vertex in self.adjacent_vertices.values():
-            nodes.append(vertex)
-
-        return nodes
+        return list(self.adjacent_vertices.values())
datastructures/graphs/edge/edge_type.py (1)

4-10: Enum looks good; consider auto() and future-proofing.

Current members are fine. Optional: use auto() to avoid manual numbering and reduce churn when adding new types. If HyperEdge won’t encode direction, introduce a generic HYPER member and align uses accordingly.

 from enum import Enum, unique
 
 @unique
 class EdgeType(Enum):
-    UNDIRECTED = 1
-    DIRECTED = 2
-    SELF = 3
-    HYPER_DIRECTED = 4
-    HYPER_UNDIRECTED = 5
+    UNDIRECTED = auto()
+    DIRECTED = auto()
+    SELF = auto()
+    HYPER_DIRECTED = auto()
+    HYPER_UNDIRECTED = auto()
datastructures/graphs/edge/edge_hyper.py (1)

13-26: Tighten generics: use List[T] where possible.

HyperEdge is Generic[T] but currently uses List[Any]. Prefer List[T] for nodes and return type.

-    def __init__(self, nodes: List[Any], ...
+    def __init__(self, nodes: List[T], ...
 ...
-    def vertices(self) -> List[Any]:
+    def vertices(self) -> List[T]:
         return self.nodes
datastructures/graphs/edge/edge_self.py (1)

19-26: Minor: property name clarity.

For a single node, consider self.node over self.node_one for clarity.

-        self.node_one = node
+        self.node = node
 ...
-        return f"{super().__str__()}, Node: {self.node_one}"
+        return f"{super().__str__()}, Node: {self.node}"
 ...
-    def vertices(self) -> List[Any]:
-        return [self.node_one]
+    def vertices(self) -> List[Any]:
+        return [self.node]
datastructures/graphs/edge/edge_directed.py (1)

27-28: Tighten generics: return List[T].

Since the edge is Generic[T], return a typed list to improve correctness.

-    def vertices(self) -> List[Any]:
+    def vertices(self) -> List[T]:
         return [self.source, self.destination]
datastructures/graphs/edge/edge_undirected.py (1)

8-11: Optional: Remove redundant Generic inheritance.

Since Edge already inherits from Generic[T], explicitly inheriting from Generic[T] again in UndirectedEdge is redundant. While not incorrect, it can be simplified.

Apply this diff to simplify the inheritance:

-class UndirectedEdge(Edge, Generic[T]):
+class UndirectedEdge(Edge[T]):
datastructures/graphs/edge/edge.py (1)

1-7: Optional: Consolidate typing imports for better organization.

The typing imports are split across lines 2 and 4, which slightly reduces readability.

Apply this diff to consolidate the imports:

 from abc import ABC, abstractmethod
-from typing import AnyStr, Union
+from typing import AnyStr, Union, Any, Dict, Optional, Generic, TypeVar, List
 from .edge_type import EdgeType
-from typing import Any, Dict, Optional, Generic, TypeVar, List
 from uuid import uuid4
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c5ac87 and 69d657a.

📒 Files selected for processing (13)
  • DIRECTORY.md (1 hunks)
  • datastructures/graphs/__init__.py (1 hunks)
  • datastructures/graphs/edge.py (0 hunks)
  • datastructures/graphs/edge/__init__.py (1 hunks)
  • datastructures/graphs/edge/edge.py (1 hunks)
  • datastructures/graphs/edge/edge_directed.py (1 hunks)
  • datastructures/graphs/edge/edge_hyper.py (1 hunks)
  • datastructures/graphs/edge/edge_self.py (1 hunks)
  • datastructures/graphs/edge/edge_type.py (1 hunks)
  • datastructures/graphs/edge/edge_undirected.py (1 hunks)
  • datastructures/graphs/graph.py (1 hunks)
  • datastructures/graphs/test_vertex.py (1 hunks)
  • datastructures/graphs/vertex.py (5 hunks)
💤 Files with no reviewable changes (1)
  • datastructures/graphs/edge.py
🧰 Additional context used
🧬 Code graph analysis (10)
datastructures/graphs/edge/edge_self.py (5)
datastructures/graphs/edge/edge.py (3)
  • edge_type (28-29)
  • Edge (9-36)
  • vertices (35-36)
datastructures/graphs/edge/edge_directed.py (2)
  • edge_type (24-25)
  • vertices (27-28)
datastructures/graphs/edge/edge_hyper.py (2)
  • edge_type (22-23)
  • vertices (25-26)
datastructures/graphs/edge/edge_undirected.py (2)
  • edge_type (23-24)
  • vertices (26-27)
datastructures/graphs/edge/edge_type.py (1)
  • EdgeType (5-10)
datastructures/graphs/edge/__init__.py (6)
datastructures/graphs/edge/edge.py (2)
  • Edge (9-36)
  • edge_type (28-29)
datastructures/graphs/edge/edge_directed.py (2)
  • edge_type (24-25)
  • DirectedEdge (8-28)
datastructures/graphs/edge/edge_hyper.py (2)
  • edge_type (22-23)
  • HyperEdge (8-26)
datastructures/graphs/edge/edge_self.py (2)
  • edge_type (22-23)
  • SelfEdge (8-26)
datastructures/graphs/edge/edge_undirected.py (2)
  • edge_type (23-24)
  • UndirectedEdge (8-27)
datastructures/graphs/edge/edge_type.py (1)
  • EdgeType (5-10)
datastructures/graphs/edge/edge.py (5)
datastructures/graphs/edge/edge_directed.py (2)
  • edge_type (24-25)
  • vertices (27-28)
datastructures/graphs/edge/edge_hyper.py (2)
  • edge_type (22-23)
  • vertices (25-26)
datastructures/graphs/edge/edge_self.py (2)
  • edge_type (22-23)
  • vertices (25-26)
datastructures/graphs/edge/edge_undirected.py (2)
  • edge_type (23-24)
  • vertices (26-27)
datastructures/graphs/edge/edge_type.py (1)
  • EdgeType (5-10)
datastructures/graphs/graph.py (3)
datastructures/stacks/__init__.py (1)
  • Stack (7-48)
datastructures/graphs/vertex.py (2)
  • Vertex (8-127)
  • neighbours (59-69)
datastructures/graphs/edge/edge.py (1)
  • Edge (9-36)
datastructures/graphs/edge/edge_hyper.py (5)
datastructures/graphs/edge/edge.py (3)
  • edge_type (28-29)
  • Edge (9-36)
  • vertices (35-36)
datastructures/graphs/edge/edge_directed.py (2)
  • edge_type (24-25)
  • vertices (27-28)
datastructures/graphs/edge/edge_self.py (2)
  • edge_type (22-23)
  • vertices (25-26)
datastructures/graphs/edge/edge_undirected.py (2)
  • edge_type (23-24)
  • vertices (26-27)
datastructures/graphs/edge/edge_type.py (1)
  • EdgeType (5-10)
datastructures/graphs/test_vertex.py (2)
datastructures/graphs/vertex.py (1)
  • Vertex (8-127)
datastructures/graphs/edge/edge_undirected.py (1)
  • UndirectedEdge (8-27)
datastructures/graphs/edge/edge_directed.py (2)
datastructures/graphs/edge/edge.py (3)
  • edge_type (28-29)
  • Edge (9-36)
  • vertices (35-36)
datastructures/graphs/edge/edge_type.py (1)
  • EdgeType (5-10)
datastructures/graphs/__init__.py (7)
datastructures/graphs/edge/edge.py (1)
  • Edge (9-36)
datastructures/graphs/edge/edge_type.py (1)
  • EdgeType (5-10)
datastructures/graphs/edge/edge_directed.py (1)
  • DirectedEdge (8-28)
datastructures/graphs/edge/edge_undirected.py (1)
  • UndirectedEdge (8-27)
datastructures/graphs/edge/edge_self.py (1)
  • SelfEdge (8-26)
datastructures/graphs/edge/edge_hyper.py (1)
  • HyperEdge (8-26)
datastructures/graphs/graph.py (1)
  • Graph (12-230)
datastructures/graphs/edge/edge_undirected.py (5)
datastructures/graphs/edge/edge.py (3)
  • edge_type (28-29)
  • Edge (9-36)
  • vertices (35-36)
datastructures/graphs/edge/edge_directed.py (2)
  • edge_type (24-25)
  • vertices (27-28)
datastructures/graphs/edge/edge_hyper.py (2)
  • edge_type (22-23)
  • vertices (25-26)
datastructures/graphs/edge/edge_self.py (2)
  • edge_type (22-23)
  • vertices (25-26)
datastructures/graphs/edge/edge_type.py (1)
  • EdgeType (5-10)
datastructures/graphs/vertex.py (2)
datastructures/graphs/edge/edge.py (1)
  • Edge (9-36)
datastructures/graphs/edge/edge_type.py (1)
  • EdgeType (5-10)
🪛 markdownlint-cli2 (0.18.1)
DIRECTORY.md

198-198: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


199-199: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


200-200: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


201-201: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


202-202: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


203-203: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


204-204: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


205-205: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


206-206: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🔇 Additional comments (8)
datastructures/graphs/__init__.py (1)

1-14: Public API facade looks good.

Re-exports are clear and usable. No action needed.

datastructures/graphs/edge/__init__.py (1)

1-6: Public exports OK; verify enum usage across modules.

Re-exports look correct. Cross-check edge_undirected.py: snippet shows EdgeType.Undirected (wrong case). Should be EdgeType.UNDIRECTED or code will break.

Suggested fix in datastructures/graphs/edge/edge_undirected.py:

-    def edge_type(self) -> EdgeType:
-        return EdgeType.Undirected
+    def edge_type(self) -> EdgeType:
+        return EdgeType.UNDIRECTED
datastructures/graphs/edge/edge_undirected.py (3)

1-6: Imports and type variable are set up correctly.

The imports are well-organized and the TypeVar declaration follows best practices for generic classes.


20-21: LGTM!

The string representation correctly extends the base class and includes the undirected edge's node information.


26-27: LGTM!

The vertices method correctly returns both nodes of the undirected edge, consistent with the pattern used in other edge implementations.

datastructures/graphs/edge/edge.py (3)

24-25: LGTM!

The string representation provides clear, informative output for debugging and logging.


27-29: LGTM!

The abstract methods edge_type and vertices correctly define the interface contract that all edge subclasses must implement. The use of @abstractmethod ensures subclasses provide concrete implementations.

Also applies to: 34-36


31-32: LGTM!

The is_unweighted helper method provides a clean API for checking whether an edge has a weight, which is useful for graph algorithms that need to distinguish between weighted and unweighted edges.

Comment on lines +14 to +18
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: uuid4() as default arg; use late binding and UUID typing.

Same issue as other edges.

-from typing import AnyStr, Union, Dict, Optional, Generic, TypeVar, List, Any
-from uuid import uuid4
+from typing import Union, Dict, Optional, Generic, TypeVar, List, Any
+from uuid import uuid4, UUID
 ...
-    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)
+    def __init__(self, source: Any, destination: Any, weight: Optional[Union[int, float]] = None,
+                 properties: Optional[Dict[str, Any]] = None,
+                 identifier: Optional[UUID] = None):
+        super().__init__(weight, properties, identifier or uuid4())

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In datastructures/graphs/edge/edge_directed.py around lines 14 to 18, the
constructor uses uuid4() as a default argument which causes a single UUID to be
shared across calls and the identifier typing should be a UUID; change the
signature to accept identifier: Optional[UUID] = None (import UUID from uuid or
typing), and inside __init__ set self.identifier = identifier or uuid4(); keep
calling super() with the resolved identifier and ensure imports and typing match
other edge classes for consistency.

Comment on lines +13 to +16
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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: uuid4() used as a default argument (evaluated at import time).

This gives the same id for all instances created without an explicit identifier.

-from typing import AnyStr, Union, Dict, Optional, Generic, TypeVar, List, Any
-from uuid import uuid4
+from typing import Union, Dict, Optional, Generic, TypeVar, List, Any
+from uuid import uuid4, UUID
 ...
-    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)
+    def __init__(self, nodes: List[Any], weight: Optional[Union[int, float]] = None,
+                 properties: Optional[Dict[str, Any]] = None,
+                 identifier: Optional[UUID] = None):
+        super().__init__(weight, properties, identifier or uuid4())

Also align base Edge to accept Optional[UUID] (see follow‑up note below).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In datastructures/graphs/edge/edge_hyper.py around lines 13 to 16, the
constructor uses uuid4() as a default argument causing the same UUID to be
shared across all instances; change the signature so identifier: Optional[UUID]
= None and inside __init__ set identifier = identifier or uuid4() before calling
super().__init__; also update the base Edge type annotations and signature to
accept Optional[UUID] (not just AnyStr) so the propagated identifier type is
Optional[UUID] and maintain existing behavior when an identifier is provided.

Comment on lines +22 to +23
def edge_type(self) -> EdgeType:
return EdgeType.SELF
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: HyperEdge returns the wrong EdgeType.

edge_type() returns EdgeType.SELF. Use a hyper variant (likely undirected unless you carry direction).

-    def edge_type(self) -> EdgeType:
-        return EdgeType.SELF
+    def edge_type(self) -> EdgeType:
+        return EdgeType.HYPER_UNDIRECTED
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def edge_type(self) -> EdgeType:
return EdgeType.SELF
def edge_type(self) -> EdgeType:
return EdgeType.HYPER_UNDIRECTED
🤖 Prompt for AI Agents
In datastructures/graphs/edge/edge_hyper.py around lines 22-23, the edge_type()
method incorrectly returns EdgeType.SELF; change it to return the hyper variant
(e.g., EdgeType.HYPER or EdgeType.UNDIRECTED_HYPER depending on the enum name
used in the codebase) so HyperEdge reports the correct type; update any imports
or references if necessary and run tests to ensure consumers expecting hyper
edges now see the correct enum value.

Comment on lines +13 to +17
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: uuid4() as default arg; switch to Optional[UUID] and late binding.

Avoid import-time evaluation and align identifier typing to UUID.

-from typing import AnyStr, Union, Dict, Optional, Generic, TypeVar, List, Any
-from uuid import uuid4
+from typing import Union, Dict, Optional, Generic, TypeVar, List, Any
+from uuid import uuid4, UUID
 ...
-    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)
+    def __init__(self, node: Any, weight: Optional[Union[int, float]] = None,
+                 properties: Optional[Dict[str, Any]] = None,
+                 identifier: Optional[UUID] = None):
+        super().__init__(weight, properties, identifier or uuid4())
🤖 Prompt for AI Agents
In datastructures/graphs/edge/edge_self.py around lines 13 to 17, the
constructor uses uuid4() as a default argument which evaluates at import time
and the identifier type should be a UUID; change the signature to accept
identifier: Optional[UUID] = None (import UUID from uuid or typing) and in the
body do identifier = uuid4() if identifier is None, then pass that UUID to
super().__init__; also update the type hints and any imports accordingly so
identifier is typed as UUID and default is lazily generated.

Comment on lines +13 to +18
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Mutable default argument and type mismatch for identifier parameter.

Two issues with the identifier parameter:

  1. Mutable default: uuid4() is called once at function definition time, causing all instances created without an explicit identifier to share the same UUID. This will lead to incorrect edge identification and potential data corruption.

  2. Type mismatch: The parameter is typed as AnyStr (which is str | bytes), but uuid4() returns a UUID object.

Apply this diff to fix both issues:

-    def __init__(self, node_one: Any, node_two: Any, weight: Optional[Union[int, float]] = None,
-                 properties: Optional[Dict[str, Any]] = None,
-                 identifier: AnyStr = uuid4()):
+    def __init__(self, node_one: Any, node_two: Any, weight: Optional[Union[int, float]] = None,
+                 properties: Optional[Dict[str, Any]] = None,
+                 identifier: Optional[Union[str, UUID]] = None):
+        if identifier is None:
+            identifier = uuid4()
         super().__init__(weight, properties, identifier)

Note: The base Edge class has the same issue and should be fixed there as well.

🤖 Prompt for AI Agents
In datastructures/graphs/edge/edge_undirected.py lines 13-18, the constructor
currently uses uuid4() as a default argument and types identifier as AnyStr;
change the signature to accept identifier: Optional[UUID] = None (import UUID
from uuid), and inside __init__ do if identifier is None: identifier = uuid4()
before calling super().__init__; this avoids a single shared UUID and matches
the UUID return type; also make the same change in the base Edge class (use
Optional[UUID] = None and assign uuid4() inside the constructor).

Comment on lines +169 to +196
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

find_all_paths(): indexes with node_one.data and constructs new Vertex() erroneously.

-        if path is None:
-            path = []
-        path = path + [node_one]
-
-        if node_one.data == node_two.data:
+        if path is None:
+            path = []
+        path = path + [node_one]
+
+        if node_one == node_two:
             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
+        paths: List[List[Vertex]] = []
+        for neighbour in self.adjacency_list.get(node_one, []):
+            if neighbour not in path:
+                new_paths = self.find_all_paths(neighbour, node_two, path)
+                paths.extend(new_paths)
+        return paths
🤖 Prompt for AI Agents
In datastructures/graphs/graph.py around lines 169-196, find_all_paths is
indexing the adjacency_list with node_one.data and constructing new Vertex(node)
when recursing; instead, use the actual Vertex objects stored in the
adjacency_list (or consistently map between data->Vertex first) so you don't
rebuild vertices or mix data keys with Vertex keys. Update the function to look
up neighbors with self.adjacency_list.get(node_one) (or translate node_one.data
to the Vertex once at the top), iterate neighbor Vertex instances, compare
neighbor != node (or compare their unique id/identity rather than .data), and
pass the neighbor Vertex directly into the recursive call (no new Vertex(...)).
Ensure adjacency_list usage is consistent (either keys/values are Vertex objects
or you convert data->Vertex once) and adjust membership checks against path to
use Vertex objects.

Comment on lines +198 to +224
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

find_shortest_path(): same keying issues as above.

-        if path is None:
-            path = []
-
-        path = path + [node_one]
-
-        if node_one.data == node_two.data:
+        if path is None:
+            path = []
+        path = path + [node_one]
+
+        if node_one == node_two:
             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
+        shortest: Union[List[Vertex], None] = None
+        for neighbour in self.adjacency_list.get(node_one, []):
+            if neighbour not in path:
+                new_path = self.find_shortest_path(neighbour, node_two, path)
+                if new_path and (shortest is None or len(new_path) < len(shortest)):
+                    shortest = new_path
+        return shortest
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 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 == node_two:
return path
shortest: Union[List[Vertex], None] = None
for neighbour in self.adjacency_list.get(node_one, []):
if neighbour not in path:
new_path = self.find_shortest_path(neighbour, node_two, path)
if new_path and (shortest is None or len(new_path) < len(shortest)):
shortest = new_path
return shortest
🤖 Prompt for AI Agents
In datastructures/graphs/graph.py around lines 198-224, the method mixes Vertex
objects and their .data keys (e.g. checking node_one.data in adjacency_list but
indexing self.adjacency_list[node_one], and comparing node.data to items in
path), causing incorrect membership/equality checks; fix by treating vertices
consistently as Vertex objects: check membership with node_one in
self.adjacency_list, compare nodes with equality on the Vertex (e.g. node_one ==
node_two), store Vertex instances in path (no .data), and use node not node.data
when checking if a neighbor is already in path or when recursing/indexing
adjacency_list.

Comment on lines +6 to +16
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()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace placeholder failing test with real Vertex checks.

Add adjacency and equality/hash tests.

-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
+class VertexTestCases(unittest.TestCase):
+    def test_add_adjacent_vertex_is_mutual(self):
+        v1 = Vertex(data=1)
+        v2 = Vertex(data=2)
+        v1.add_adjacent_vertex(v2)
+        self.assertIn(v2, v1.neighbours)
+        self.assertIn(v1, v2.neighbours)
+
+    def test_eq_and_hash_by_id(self):
+        v1 = Vertex(data=1)
+        v2 = Vertex(data=99, identifier=v1.id)
+        self.assertEqual(v1, v2)
+        self.assertIn(v2, {v1})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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()
class VertexTestCases(unittest.TestCase):
def test_add_adjacent_vertex_is_mutual(self):
v1 = Vertex(data=1)
v2 = Vertex(data=2)
v1.add_adjacent_vertex(v2)
self.assertIn(v2, v1.neighbours)
self.assertIn(v1, v2.neighbours)
def test_eq_and_hash_by_id(self):
v1 = Vertex(data=1)
v2 = Vertex(data=99, identifier=v1.id)
self.assertEqual(v1, v2)
self.assertIn(v2, {v1})
if __name__ == '__main__':
unittest.main()
🤖 Prompt for AI Agents
In datastructures/graphs/test_vertex.py around lines 6 to 16, replace the
failing placeholder assertion with real tests: create two Vertex instances and
an UndirectedEdge between them, then assert that each vertex's
adjacency/container includes the other (and the edge if your API stores edges),
assert that two Vertex instances constructed with the same data compare equal
and produce the same hash, and assert that vertices with different data are not
equal; use assertTrue/assertFalse/assertEqual/assertIn/assertNotIn as
appropriate to validate adjacency and equality/hash behavior.

Comment on lines 13 to 32
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
self.outgoing_edges = outgoing_edges
self.edges = self.incoming_edges.union(self.outgoing_edges)
self.adjacent_vertices: Dict[str, 'Vertex'] = {}
self.properties = properties

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Default-id bug, stale edges, and key typing. Make Vertex hashable.

  • Avoid uuid4() as a default arg (same id across instances).
  • Don’t cache edges; compute union on demand.
  • adjacent_vertices key type should match id type.
  • Add hash to use Vertex as dict key (Graph uses it).
-from .edge import Edge, EdgeType
+from .edge import Edge, EdgeType, DirectedEdge
@@
-    def __init__(
+    def __init__(
         self,
         data: T,
-        identifier: Any = uuid4(),
+        identifier: Any = None,
         properties: Optional[Dict[str, Any]] = None,
         incoming_edges: Optional[Set[Edge]] = None,
         outgoing_edges: Optional[Set[Edge]] = None,
     ):
@@
-        self.id = identifier
+        self.id = uuid4() if identifier is None else identifier
         self.data = data
@@
-        self.edges = self.incoming_edges.union(self.outgoing_edges)
-        self.adjacent_vertices: Dict[str, 'Vertex'] = {}
+        self.adjacent_vertices: Dict[Any, 'Vertex'] = {}
         self.properties = properties
+
+    @property
+    def edges(self) -> Set[Edge]:
+        return self.incoming_edges | self.outgoing_edges
@@
-    def __eq__(self, other: "Vertex") -> bool:
-        return self.id == other.id
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, Vertex):
+            return NotImplemented
+        return self.id == other.id
+
+    def __hash__(self) -> int:
+        return hash(self.id)

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +198 to +206
* 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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix list indentation to satisfy MD007 and match surrounding style.

Current bullets under “Graphs > Edge” are over‑indented. Apply:

-    * 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)
+  * 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)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* 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)
* 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)
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

198-198: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


199-199: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


200-200: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


201-201: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


202-202: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


203-203: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


204-204: Unordered list indentation
Expected: 4; Actual: 6

(MD007, ul-indent)


205-205: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


206-206: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🤖 Prompt for AI Agents
In DIRECTORY.md around lines 198 to 206, the six bullet links under the "Edge"
subheading are over‑indented and break MD007; unindent those lines so the list
items for Edge are at the same indentation level as the following top-level list
entries (e.g., Graph and Test Vertex). Ensure the bullets use the same number of
leading spaces/tabs as the surrounding lists (consistent indentation style) so
the nested list structure and MD007 lint rule are satisfied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Algorithm Algorithm Problem Datastructures Datastructures Documentation Documentation Updates Graph Graph data structures and algorithms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants