Skip to content

⚡️ Speed up function find_node_with_highest_degree by 2,940% #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

codeflash-ai[bot]
Copy link

@codeflash-ai codeflash-ai bot commented Jul 30, 2025

📄 2,940% (29.40x) speedup for find_node_with_highest_degree in src/dsa/nodes.py

⏱️ Runtime : 34.0 milliseconds 1.12 milliseconds (best of 1028 runs)

📝 Explanation and details

The optimized code achieves a 2939% speedup by eliminating the nested loop that was causing O(n²) behavior in the original implementation.

Key Optimization: Precomputing In-Degrees

The original code counted incoming connections by iterating through all connection entries for every node:

for src, targets in connections.items():  # O(C) connections 
    if node in targets:                   # O(T) targets per connection
        degree += 1

This created O(N × C × T) complexity where N=nodes, C=connections, T=targets per connection.

The optimized version precomputes all in-degrees in a single pass:

in_degree = {}
for targets in connections.values():      # O(C) - once only
    for tgt in targets:                   # O(T) 
        in_degree[tgt] = in_degree.get(tgt, 0) + 1

Then simply looks up each node's in-degree in O(1): in_deg = in_degree.get(node, 0)

Performance Impact Analysis:

From the line profiler results, the bottleneck was eliminated:

  • Original: Lines with nested loops consumed 98.4% of runtime (52.2% + 46.2%)
  • Optimized: The precomputation phase takes only 58.3% of total time (29% + 29.3%), but runs once instead of N times

Complexity Improvement:

  • Original: O(N × C × T) where each node triggers a full scan of all connections
  • Optimized: O(C × T + N) - single precomputation pass plus linear node processing

Test Case Performance Patterns:

The optimization shows dramatic improvements on larger, denser graphs:

  • Large complete graph (100 nodes): 1151% faster - eliminates O(n³) behavior
  • Large chain graph (1000 nodes): 13940% faster - reduces O(n²) to O(n)
  • Large sparse graphs: 2513% faster - benefits from single-pass preprocessing

For small graphs (≤3 nodes), the optimization shows modest 5-15% gains or even slight regressions due to preprocessing overhead, but this is negligible compared to the massive gains on realistic graph sizes.

The optimization is particularly effective for graphs with many connections or high in-degrees, where the original nested loop would perform many redundant scans.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 43 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 1 Passed
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import pytest  # used for our unit tests
from src.dsa.nodes import find_node_with_highest_degree

# unit tests

# ------------------------------
# 1. Basic Test Cases
# ------------------------------

def test_single_node_no_connections():
    # Single node, no connections
    nodes = ['A']
    connections = {}
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 375ns -> 417ns (10.1% slower)

def test_two_nodes_one_connection():
    # Two nodes, one connection from A to B
    nodes = ['A', 'B']
    connections = {'A': ['B']}
    # Both have degree 1, but A is checked first
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 666ns -> 625ns (6.56% faster)

def test_three_nodes_varied_connections():
    # Three nodes, varied connections
    nodes = ['A', 'B', 'C']
    connections = {
        'A': ['B', 'C'],
        'B': [],
        'C': ['A']
    }
    # A: out=2, in=1 (from C) -> degree=3
    # B: out=0, in=1 (from A) -> degree=1
    # C: out=1, in=1 (from A) -> degree=2
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 875ns -> 792ns (10.5% faster)

def test_multiple_nodes_tie():
    # Multiple nodes with a tie in degree
    nodes = ['A', 'B', 'C']
    connections = {
        'A': ['B'],
        'B': ['C'],
        'C': ['A']
    }
    # All have degree 2 (1 out, 1 in)
    # Should return 'A' (first in list)
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 833ns -> 792ns (5.18% faster)

def test_node_with_only_incoming_connections():
    nodes = ['A', 'B', 'C']
    connections = {
        'A': ['C'],
        'B': ['C'],
        'C': []
    }
    # C: in=2, out=0 -> degree=2
    # A, B: out=1, in=0 -> degree=1
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 833ns -> 791ns (5.31% faster)

def test_node_with_only_outgoing_connections():
    nodes = ['A', 'B', 'C']
    connections = {
        'A': ['B', 'C'],
        'B': [],
        'C': []
    }
    # A: out=2, in=0 -> degree=2
    # B, C: out=0, in=1 -> degree=1
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 833ns -> 792ns (5.18% faster)

# ------------------------------
# 2. Edge Test Cases
# ------------------------------

def test_empty_nodes_and_connections():
    # No nodes at all
    nodes = []
    connections = {}
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 125ns -> 250ns (50.0% slower)

def test_nodes_with_no_connections_key():
    # Nodes present, but connections dict is empty
    nodes = ['A', 'B', 'C']
    connections = {}
    # All degrees are 0, should return 'A'
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 583ns -> 541ns (7.76% faster)

def test_node_with_self_loop():
    # Node with a self-loop
    nodes = ['A', 'B']
    connections = {'A': ['A'], 'B': []}
    # A: out=1, in=1 (self-loop counts as both) -> degree=2
    # B: out=0, in=0 -> degree=0
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 667ns -> 708ns (5.79% slower)

def test_node_not_in_connections_dict():
    # Node present in nodes, but not in connections dict
    nodes = ['A', 'B']
    connections = {'A': ['B']}
    # B is not a key in connections
    # A: out=1, in=0 -> degree=1
    # B: out=0, in=1 -> degree=1
    # Tie, should return 'A'
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 625ns -> 625ns (0.000% faster)

def test_disconnected_nodes():
    # All nodes are disconnected
    nodes = ['A', 'B', 'C']
    connections = {'A': [], 'B': [], 'C': []}
    # All have degree 0, should return 'A'
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 750ns -> 667ns (12.4% faster)

def test_multiple_self_loops():
    # Multiple nodes with self-loops
    nodes = ['A', 'B', 'C']
    connections = {'A': ['A'], 'B': ['B'], 'C': ['C']}
    # All have degree 2 (self-loop counts as both in and out)
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 916ns -> 791ns (15.8% faster)

def test_duplicate_connections():
    # Duplicate edges in connections
    nodes = ['A', 'B']
    connections = {'A': ['B', 'B'], 'B': []}
    # A: out=2, in=0 -> degree=2
    # B: out=0, in=2 (from A) -> degree=2
    # Tie, should return 'A'
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 708ns -> 750ns (5.60% slower)

def test_node_with_incoming_and_outgoing_to_same_node():
    # A -> B, B -> A
    nodes = ['A', 'B']
    connections = {'A': ['B'], 'B': ['A']}
    # Both: out=1, in=1 -> degree=2
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 666ns -> 667ns (0.150% slower)

def test_node_with_no_targets_but_incoming():
    # Node not a key in connections, but appears as target
    nodes = ['A', 'B']
    connections = {'A': ['B']}
    # B: out=0, in=1 -> degree=1
    # A: out=1, in=0 -> degree=1
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 625ns -> 666ns (6.16% slower)

def test_connections_with_nonexistent_targets():
    # Connections point to nodes not in the nodes list
    nodes = ['A', 'B']
    connections = {'A': ['B', 'C'], 'B': ['D']}
    # C and D are not in nodes, should ignore them
    # A: out=2, in=0 -> degree=2
    # B: out=1, in=1 (from A) -> degree=2
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 708ns -> 709ns (0.141% slower)

def test_nodes_with_nonexistent_keys():
    # Connections dict has keys not in nodes
    nodes = ['A', 'B']
    connections = {'A': ['B'], 'B': [], 'C': ['A']}
    # C is not in nodes, but its outgoing edges can affect A's in-degree
    # A: out=1, in=1 (from C) -> degree=2
    # B: out=0, in=1 (from A) -> degree=1
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 667ns -> 709ns (5.92% slower)

# ------------------------------
# 3. Large Scale Test Cases
# ------------------------------

def test_large_fully_connected_graph():
    # 100 nodes, each connected to every other node (excluding self)
    n = 100
    nodes = [f'N{i}' for i in range(n)]
    connections = {node: [f'N{j}' for j in range(n) if j != i] for i, node in enumerate(nodes)}
    # Each node: out=n-1, in=n-1 -> degree=2*(n-1)
    # All degrees equal, should return first node
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 2.85ms -> 354μs (705% faster)

def test_large_sparse_graph():
    # 1000 nodes, only first node connects to all others
    n = 1000
    nodes = [f'N{i}' for i in range(n)]
    connections = {nodes[0]: nodes[1:]}
    # N0: out=n-1, in=0 -> degree=n-1
    # N1..N999: out=0, in=1 -> degree=1
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 2.14ms -> 81.8μs (2513% faster)

def test_large_chain_graph():
    # 500 nodes in a chain: N0->N1->N2->...->N499
    n = 500
    nodes = [f'N{i}' for i in range(n)]
    connections = {f'N{i}': [f'N{i+1}'] for i in range(n-1)}
    # N0: out=1, in=0 -> degree=1
    # N1..N498: out=1, in=1 -> degree=2
    # N499: out=0, in=1 -> degree=1
    # N1 is first with degree 2
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 3.79ms -> 58.5μs (6378% faster)

def test_large_disconnected_graph():
    # 1000 nodes, no connections
    n = 1000
    nodes = [f'N{i}' for i in range(n)]
    connections = {}
    # All degrees 0, should return first node
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 64.1μs -> 39.2μs (63.6% faster)

def test_large_graph_with_self_loops():
    # 100 nodes, each with a self-loop
    n = 100
    nodes = [f'N{i}' for i in range(n)]
    connections = {node: [node] for node in nodes}
    # Each node: out=1, in=1 (self-loop) -> degree=2
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 172μs -> 9.92μs (1639% faster)

def test_large_graph_with_duplicate_edges():
    # 500 nodes, first node connects twice to every other node
    n = 500
    nodes = [f'N{i}' for i in range(n)]
    connections = {nodes[0]: [node for node in nodes[1:] for _ in (0,1)]}
    # N0: out=2*(n-1), in=0 -> degree=2*(n-1)
    # N1..N499: out=0, in=2 (from N0) -> degree=2
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 1.05ms -> 52.4μs (1904% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

import pytest  # used for our unit tests
from src.dsa.nodes import find_node_with_highest_degree

# unit tests

# ------------------------
# Basic Test Cases
# ------------------------

def test_single_node_no_connections():
    # One node, no connections
    nodes = ["A"]
    connections = {}
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 375ns -> 458ns (18.1% slower)

def test_two_nodes_one_connection():
    # Two nodes, one connection from A to B
    nodes = ["A", "B"]
    connections = {"A": ["B"]}
    # A has degree 1 (out), B has degree 1 (in), tie: should return first in nodes order with max degree ("A")
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 666ns -> 666ns (0.000% faster)

def test_two_nodes_bidirectional():
    # Two nodes, bidirectional connection
    nodes = ["A", "B"]
    connections = {"A": ["B"], "B": ["A"]}
    # Both have degree 2 (1 in, 1 out)
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 666ns -> 667ns (0.150% slower)

def test_three_nodes_varied_connections():
    # Three nodes, varied connections
    nodes = ["A", "B", "C"]
    connections = {"A": ["B", "C"], "B": ["A"], "C": []}
    # A: 2 out, 1 in = 3; B: 1 out, 1 in = 2; C: 0 out, 1 in = 1
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 875ns -> 833ns (5.04% faster)

def test_node_with_no_connections_among_others():
    # One node with no connections, others with connections
    nodes = ["A", "B", "C"]
    connections = {"A": ["B"], "B": ["C"], "C": []}
    # A: 1 out, 0 in = 1; B: 1 out, 1 in = 2; C: 0 out, 1 in = 1
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 875ns -> 792ns (10.5% faster)

# ------------------------
# Edge Test Cases
# ------------------------

def test_empty_nodes_list():
    # No nodes at all
    nodes = []
    connections = {}
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 125ns -> 291ns (57.0% slower)

def test_connections_with_nonexistent_nodes():
    # Connections refer to nodes not in 'nodes' list
    nodes = ["A"]
    connections = {"A": ["B"], "B": ["A"]}
    # Only "A" considered, degree: 1 out (to B), 1 in (from B)
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 541ns -> 625ns (13.4% slower)

def test_self_loops():
    # Nodes with self-loops
    nodes = ["A", "B"]
    connections = {"A": ["A"], "B": ["B", "A"]}
    # A: 1 out (to self), 1 in (from self), 1 in (from B) = 3
    # B: 1 out (to self), 0 in = 1
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 708ns -> 750ns (5.60% slower)

def test_multiple_nodes_same_max_degree():
    # Multiple nodes with the same highest degree (tie)
    nodes = ["A", "B", "C"]
    connections = {"A": ["B"], "B": ["C"], "C": ["A"]}
    # All nodes have degree 2 (1 out, 1 in)
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 834ns -> 750ns (11.2% faster)

def test_node_with_only_incoming_connections():
    # Node only has incoming connections, no outgoing
    nodes = ["A", "B", "C"]
    connections = {"A": ["C"], "B": ["C"]}
    # A: 1 out, 0 in = 1; B: 1 out, 0 in = 1; C: 0 out, 2 in = 2
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 875ns -> 792ns (10.5% faster)

def test_node_with_only_outgoing_connections():
    # Node only has outgoing connections, no incoming
    nodes = ["A", "B", "C"]
    connections = {"A": ["B", "C"]}
    # A: 2 out, 0 in = 2; B: 0 out, 1 in = 1; C: 0 out, 1 in = 1
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 792ns -> 791ns (0.126% faster)

def test_disconnected_graph():
    # All nodes are disconnected
    nodes = ["A", "B", "C"]
    connections = {}
    # All have degree 0, should return "A" (first in list)
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 542ns -> 542ns (0.000% faster)

def test_duplicate_connections():
    # Duplicate connections (should count each occurrence)
    nodes = ["A", "B"]
    connections = {"A": ["B", "B"]}
    # A: 2 out, 0 in = 2; B: 0 out, 2 in = 2
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 667ns -> 708ns (5.79% slower)

def test_nodes_with_empty_connection_lists():
    # Nodes with explicit empty lists
    nodes = ["A", "B"]
    connections = {"A": [], "B": []}
    # Both have degree 0
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 583ns -> 583ns (0.000% faster)

def test_nodes_with_missing_connection_entries():
    # Some nodes missing from connections dict
    nodes = ["A", "B", "C"]
    connections = {"A": ["B"]}
    # B, C missing from connections
    # A: 1 out, 0 in = 1; B: 0 out, 1 in = 1; C: 0 out, 0 in = 0
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 750ns -> 708ns (5.93% faster)

# ------------------------
# Large Scale Test Cases
# ------------------------

def test_large_complete_graph():
    # Complete graph: every node connected to every other node (no self-loops)
    n = 100
    nodes = [f"N{i}" for i in range(n)]
    connections = {node: [other for other in nodes if other != node] for node in nodes}
    # Each node: n-1 out, n-1 in = 2*(n-1)
    # All have same degree, should return first node
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 2.72ms -> 217μs (1151% faster)

def test_large_sparse_graph():
    # Large sparse graph: only a few connections
    n = 1000
    nodes = [f"N{i}" for i in range(n)]
    # Only first node connects to next 10 nodes
    connections = {"N0": [f"N{i}" for i in range(1, 11)]}
    # N0: 10 out, 0 in = 10; N1-N10: 0 out, 1 in = 1; rest: 0
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 109μs -> 52.2μs (110% faster)

def test_large_chain_graph():
    # Large chain: N0->N1->N2->...->N999
    n = 1000
    nodes = [f"N{i}" for i in range(n)]
    connections = {f"N{i}": [f"N{i+1}"] for i in range(n-1)}
    # N0: 1 out, 0 in = 1; N1-N998: 1 out, 1 in = 2; N999: 0 out, 1 in = 1
    # N1 is first with degree 2
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 16.2ms -> 115μs (13940% faster)

def test_large_graph_with_self_loops():
    # Each node has a self-loop, plus one outgoing to next node
    n = 500
    nodes = [f"N{i}" for i in range(n)]
    connections = {f"N{i}": [f"N{i}", f"N{(i+1)%n}"] for i in range(n)}
    # Each node: 2 out (self, next), 1 in (from previous), 1 in (self) = 3
    # All nodes have same degree, should return "N0"
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 4.76ms -> 75.1μs (6245% faster)

def test_large_disconnected_graph():
    # Large graph, all nodes disconnected
    n = 1000
    nodes = [f"N{i}" for i in range(n)]
    connections = {}
    # All have degree 0, should return "N0"
    codeflash_output = find_node_with_highest_degree(nodes, connections) # 63.8μs -> 39.0μs (63.5% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

from src.dsa.nodes import find_node_with_highest_degree

def test_find_node_with_highest_degree():
    find_node_with_highest_degree(['\x01', '\x01\x00'], {'\x00\x00': [], '\x00\x00\x00': ['\x01'], '\x00': [], '\x02': []})

To edit these changes git checkout codeflash/optimize-find_node_with_highest_degree-mdpdzwh0 and push.

Codeflash

The optimized code achieves a **2939% speedup** by eliminating the nested loop that was causing O(n²) behavior in the original implementation.

**Key Optimization: Precomputing In-Degrees**

The original code counted incoming connections by iterating through all connection entries for every node:
```python
for src, targets in connections.items():  # O(C) connections 
    if node in targets:                   # O(T) targets per connection
        degree += 1
```
This created O(N × C × T) complexity where N=nodes, C=connections, T=targets per connection.

The optimized version precomputes all in-degrees in a single pass:
```python
in_degree = {}
for targets in connections.values():      # O(C) - once only
    for tgt in targets:                   # O(T) 
        in_degree[tgt] = in_degree.get(tgt, 0) + 1
```
Then simply looks up each node's in-degree in O(1): `in_deg = in_degree.get(node, 0)`

**Performance Impact Analysis:**

From the line profiler results, the bottleneck was eliminated:
- **Original**: Lines with nested loops consumed 98.4% of runtime (52.2% + 46.2%)
- **Optimized**: The precomputation phase takes only 58.3% of total time (29% + 29.3%), but runs once instead of N times

**Complexity Improvement:**
- **Original**: O(N × C × T) where each node triggers a full scan of all connections
- **Optimized**: O(C × T + N) - single precomputation pass plus linear node processing

**Test Case Performance Patterns:**

The optimization shows dramatic improvements on larger, denser graphs:
- **Large complete graph (100 nodes)**: 1151% faster - eliminates O(n³) behavior
- **Large chain graph (1000 nodes)**: 13940% faster - reduces O(n²) to O(n)  
- **Large sparse graphs**: 2513% faster - benefits from single-pass preprocessing

For small graphs (≤3 nodes), the optimization shows modest 5-15% gains or even slight regressions due to preprocessing overhead, but this is negligible compared to the massive gains on realistic graph sizes.

The optimization is particularly effective for graphs with many connections or high in-degrees, where the original nested loop would perform many redundant scans.
@codeflash-ai codeflash-ai bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Jul 30, 2025
@codeflash-ai codeflash-ai bot requested a review from aseembits93 July 30, 2025 03:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⚡️ codeflash Optimization PR opened by Codeflash AI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

0 participants