Graph Traversal
Graph traversal means navigating through the graph by following relationships.
Neighbors
Get directly connected nodes.
Basic Neighbors
# Get all outgoing neighbors
friends = db.get_neighbors(alice.id, direction='outgoing')
# Get all incoming neighbors (who points to Alice)
followers = db.get_neighbors(alice.id, direction='incoming')
# Get neighbors in both directions
all_neighbors = db.get_neighbors(alice.id, direction='both')
Filtered by Relationship Type
# Only coworkers
coworkers = db.get_neighbors(
alice.id,
direction='outgoing',
rel_type='WORKS_WITH'
)
# Only direct reports
reports = db.get_neighbors(
manager.id,
direction='outgoing',
rel_type='MANAGES'
)
# Only managers (incoming)
managers = db.get_neighbors(
employee.id,
direction='incoming',
rel_type='MANAGES'
)
Path Finding
Find routes between nodes.
Shortest Path (BFS)
Breadth-first search finds the shortest path in terms of number of hops:
# Find shortest path
path = db.find_shortest_path(alice.id, bob.id)
if path:
print(f"Path length: {len(path)} nodes")
for node in path:
print(f" -> {node.properties['name']}")
else:
print("No path found")
# Example output:
# Path length: 3 nodes
# -> Alice
# -> Charlie
# -> Bob
Any Path (DFS)
Depth-first search finds any path with optional depth limit:
# Find any path with max depth
path = db.find_path(alice.id, bob.id, max_depth=5)
if path:
names = [n.properties['name'] for n in path]
print(f"Path: {' -> '.join(names)}")
Path with Cypher
More complex path queries using Cypher:
# Shortest path with specific relationship type
results = db.execute("""
MATCH p = shortestPath(
(a:Person {name: 'Alice'})-[:KNOWS*1..5]->(b:Person {name: 'Bob'})
)
RETURN p, length(p) as hops
""")
# All shortest paths
results = db.execute("""
MATCH p = allShortestPaths(
(a:Person {name: 'Alice'})-[:KNOWS*1..5]->(b:Person {name: 'Bob'})
)
RETURN p
""")
Variable-Length Patterns
Match patterns with variable path lengths.
Basic Variable Length
# Friends of friends (2 hops)
fof = db.execute("""
MATCH (a:Person {name: 'Alice'})-[:KNOWS*2]->(fof:Person)
WHERE fof <> a
RETURN DISTINCT fof.name
""")
# 1 to 3 hops
network = db.execute("""
MATCH (a:Person {name: 'Alice'})-[:KNOWS*1..3]->(other:Person)
WHERE other <> a
RETURN other.name, length(p) as distance
""")
Unbounded Paths
Use with caution - always configure a maximum:
# Configure max hops when creating database
db = GrafitoDatabase(':memory:', cypher_max_hops=5)
# This uses the configured max
db.execute("MATCH (a)-[:KNOWS*..]->(b) RETURN b")
Traversal Strategies
Top-Down Hierarchy
def get_all_reports(db, manager_id, max_depth=5):
"""Get all employees in reporting chain."""
results = db.execute("""
MATCH (mgr:Person {id: $manager_id})<-[:REPORTS_TO*1..$max_depth]-(emp:Person)
RETURN emp, length(p) as level
""", {'manager_id': manager_id, 'max_depth': max_depth})
return [(row['emp'], row['level']) for row in results]
Bottom-Up Ancestry
def get_management_chain(db, employee_id):
"""Get chain of command up to CEO."""
results = db.execute("""
MATCH (emp:Person {id: $employee_id})-[:REPORTS_TO*]->(manager:Person)
RETURN manager.name
""", {'employee_id': employee_id})
return [row['manager.name'] for row in results]
Circular Detection
def has_circular_reference(db, node_id, rel_type):
"""Check if node participates in a cycle."""
try:
results = db.execute("""
MATCH (n {id: $node_id})-[:$rel_type*]->(n)
RETURN count(*) as cycles
""", {'node_id': node_id, 'rel_type': rel_type})
return results[0]['cycles'] > 0
except:
return False
Common Algorithms
Degree Centrality
def get_most_connected(db, label='Person', limit=10):
"""Find nodes with most connections."""
results = db.execute("""
MATCH (n:$label)-[r]-()
RETURN n.name, count(r) as degree
ORDER BY degree DESC
LIMIT $limit
""", {'label': label, 'limit': limit})
return results
Common Neighbors
def common_neighbors(db, node1_id, node2_id):
"""Find nodes connected to both."""
results = db.execute("""
MATCH (n1 {id: $id1})-->(common)<--(n2 {id: $id2})
RETURN DISTINCT common
""", {'id1': node1_id, 'id2': node2_id})
return [row['common'] for row in results]
Graph Diameter
def estimate_diameter(db):
"""Estimate longest shortest path in graph."""
results = db.execute("""
MATCH (a:Person), (b:Person)
WHERE a <> b
MATCH p = shortestPath((a)-[:KNOWS*]->(b))
RETURN max(length(p)) as diameter
""")
return results[0]['diameter'] if results else 0
Performance Considerations
1. Limit Path Length
# Good: Bounded search
results = db.execute("MATCH (a)-[:KNOWS*1..3]->(b) RETURN b")
# Risky: Unbounded
results = db.execute("MATCH (a)-[:KNOWS*..]->(b) RETURN b") # Uses default max
2. Use Indexes
# Create index for faster lookups
db.create_node_index('Person', 'name')
# Query uses index
results = db.execute("MATCH (n:Person {name: 'Alice'})-[:KNOWS]->(b) RETURN b")
3. Filter Early
# Good: Filter before expanding
results = db.execute("""
MATCH (n:Person {active: true})-[:KNOWS]->(b)
WHERE n.created_at > $date
RETURN b
""")
# Less efficient: Expand then filter
results = db.execute("""
MATCH (n)-[:KNOWS]->(b)
WHERE n:Person AND n.active = true AND n.created_at > $date
RETURN b
""")
Examples
Recommendation Engine
def recommend_friends(db, user_id):
"""Friend of friends who aren't already friends."""
results = db.execute("""
MATCH (me:Person {id: $user_id})-[:KNOWS]->(friend)-[:KNOWS]->(fof)
WHERE fof <> me
AND NOT (me)-[:KNOWS]->(fof)
RETURN fof.name, count(friend) as mutual_friends
ORDER BY mutual_friends DESC
LIMIT 10
""", {'user_id': user_id})
return results
Supply Chain
def get_suppliers(db, product_id, tier=2):
"""Get n-tier suppliers."""
results = db.execute("""
MATCH (p:Product {id: $product_id})-[:REQUIRES*1..$tier]->(s:Supplier)
RETURN DISTINCT s, min(length(p)) as tier
""", {'product_id': product_id, 'tier': tier})
return results
Content Navigation
def related_content(db, article_id):
"""Find content through multiple relationship types."""
results = db.execute("""
MATCH (a:Article {id: $id})-[:TAGGED]->(tag)<-[:TAGGED]-(related:Article)
WHERE related <> a
RETURN related, count(tag) as shared_tags
ORDER BY shared_tags DESC
LIMIT 5
""", {'id': article_id})
return results