Skip to content

Commit 3805ef3

Browse files
feat: Implement LeetCode router problem notes
Co-authored-by: jhw1994 <jhw1994@gmail.com>
1 parent 403eb09 commit 3805ef3

1 file changed

Lines changed: 288 additions & 0 deletions

File tree

‎notes/leetcode/implement-router.md‎

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
---
2+
title: "Implement Router"
3+
date: "2025-01-27"
4+
tags: ["Hash Table", "String", "Design"]
5+
related: []
6+
slug: "implement-router"
7+
category: "leetcode"
8+
leetcode_url: "https://leetcode.com/problems/implement-router/description/"
9+
leetcode_difficulty: "Medium"
10+
leetcode_topics: ["Hash Table", "String", "Design"]
11+
---
12+
13+
# Implement Router
14+
15+
## Summary
16+
Design and implement a basic HTTP router that can register routes with different HTTP methods and handle incoming requests by matching them to the appropriate handler functions.
17+
18+
## Problem Description
19+
Implement a Router class that supports:
20+
21+
1. **Route Registration**: Register routes with specific HTTP methods (GET, POST, PUT, DELETE, etc.)
22+
2. **Route Matching**: Match incoming requests to registered routes
23+
3. **Handler Execution**: Execute the appropriate handler function for matched routes
24+
4. **404 Handling**: Return appropriate response for unmatched routes
25+
26+
**Key Requirements:**
27+
- Support multiple HTTP methods
28+
- Handle exact path matching
29+
- Support path parameters (e.g., `/users/:id`)
30+
- Return appropriate HTTP status codes
31+
- Handle edge cases like duplicate routes, invalid methods
32+
33+
**Example Usage:**
34+
```python
35+
router = Router()
36+
37+
# Register routes
38+
router.add_route('GET', '/users', get_users_handler)
39+
router.add_route('GET', '/users/:id', get_user_handler)
40+
router.add_route('POST', '/users', create_user_handler)
41+
42+
# Handle requests
43+
response = router.handle_request('GET', '/users')
44+
response = router.handle_request('GET', '/users/123')
45+
response = router.handle_request('POST', '/users')
46+
```
47+
48+
## Solution Approach
49+
This problem can be solved using a hash table-based approach with support for path parameters:
50+
51+
### Core Data Structure
52+
- Use a dictionary where keys are HTTP methods and values are dictionaries of route patterns to handlers
53+
- Store both exact matches and parameterized routes
54+
- Use regex patterns for parameter matching
55+
56+
### Algorithm Steps
57+
1. **Route Registration**:
58+
- Parse route pattern to identify parameters (e.g., `:id`)
59+
- Convert to regex pattern for matching
60+
- Store handler function with compiled regex
61+
62+
2. **Route Matching**:
63+
- Look up routes for the given HTTP method
64+
- Try exact matches first
65+
- Try parameterized routes using regex matching
66+
- Extract parameter values from matched route
67+
68+
3. **Handler Execution**:
69+
- Execute the matched handler with extracted parameters
70+
- Return appropriate response object
71+
72+
## Time & Space Complexity
73+
- **Time Complexity:**
74+
- Route registration: O(1) average case
75+
- Route matching: O(n) where n is number of registered routes for the method
76+
- Handler execution: O(1)
77+
- **Space Complexity:** O(m × r) where m is number of HTTP methods and r is number of routes per method
78+
79+
## Key Insights
80+
- **Exact match priority**: Check exact matches before parameterized routes
81+
- **Parameter extraction**: Use regex groups to extract parameter values
82+
- **Method separation**: Organize routes by HTTP method for efficient lookup
83+
- **Error handling**: Proper 404 and 405 (Method Not Allowed) responses
84+
- **Route conflicts**: Handle duplicate route registrations appropriately
85+
86+
## Examples / snippets
87+
88+
### Solution Code
89+
```python
90+
import re
91+
from typing import Dict, Callable, Optional, Tuple, Any
92+
from collections import defaultdict
93+
94+
class Router:
95+
def __init__(self):
96+
# Dictionary: method -> {pattern: (handler, compiled_regex, param_names)}
97+
self.routes = defaultdict(dict)
98+
# Dictionary: method -> {exact_path: handler} for exact matches
99+
self.exact_routes = defaultdict(dict)
100+
101+
def add_route(self, method: str, pattern: str, handler: Callable) -> None:
102+
"""Register a route with the given method, pattern, and handler."""
103+
method = method.upper()
104+
105+
# Check for exact match (no parameters)
106+
if ':' not in pattern:
107+
if pattern in self.exact_routes[method]:
108+
raise ValueError(f"Route {method} {pattern} already exists")
109+
self.exact_routes[method][pattern] = handler
110+
return
111+
112+
# Handle parameterized routes
113+
if pattern in self.routes[method]:
114+
raise ValueError(f"Route {method} {pattern} already exists")
115+
116+
# Convert pattern to regex and extract parameter names
117+
param_names = []
118+
regex_pattern = pattern
119+
120+
# Find all :param patterns
121+
param_matches = re.findall(r':(\w+)', pattern)
122+
param_names = param_matches
123+
124+
# Replace :param with regex groups
125+
regex_pattern = re.sub(r':(\w+)', r'([^/]+)', pattern)
126+
regex_pattern = f'^{regex_pattern}$'
127+
128+
compiled_regex = re.compile(regex_pattern)
129+
self.routes[method][pattern] = (handler, compiled_regex, param_names)
130+
131+
def handle_request(self, method: str, path: str) -> Dict[str, Any]:
132+
"""Handle an incoming request and return the response."""
133+
method = method.upper()
134+
135+
# Try exact match first
136+
if path in self.exact_routes[method]:
137+
handler = self.exact_routes[method][path]
138+
return self._execute_handler(handler, {})
139+
140+
# Try parameterized routes
141+
for pattern, (handler, compiled_regex, param_names) in self.routes[method].items():
142+
match = compiled_regex.match(path)
143+
if match:
144+
# Extract parameter values
145+
params = {}
146+
for i, param_name in enumerate(param_names):
147+
params[param_name] = match.group(i + 1)
148+
149+
return self._execute_handler(handler, params)
150+
151+
# Check if method exists but path doesn't (405 Method Not Allowed)
152+
if self.exact_routes[method] or self.routes[method]:
153+
return {
154+
'status_code': 405,
155+
'message': 'Method Not Allowed',
156+
'data': None
157+
}
158+
159+
# Route not found (404 Not Found)
160+
return {
161+
'status_code': 404,
162+
'message': 'Not Found',
163+
'data': None
164+
}
165+
166+
def _execute_handler(self, handler: Callable, params: Dict[str, str]) -> Dict[str, Any]:
167+
"""Execute the handler function with extracted parameters."""
168+
try:
169+
if params:
170+
result = handler(**params)
171+
else:
172+
result = handler()
173+
174+
return {
175+
'status_code': 200,
176+
'message': 'OK',
177+
'data': result
178+
}
179+
except Exception as e:
180+
return {
181+
'status_code': 500,
182+
'message': 'Internal Server Error',
183+
'data': str(e)
184+
}
185+
186+
def list_routes(self) -> Dict[str, list]:
187+
"""List all registered routes for debugging."""
188+
all_routes = {}
189+
for method in self.exact_routes:
190+
all_routes[method] = list(self.exact_routes[method].keys())
191+
for method in self.routes:
192+
if method not in all_routes:
193+
all_routes[method] = []
194+
all_routes[method].extend(self.routes[method].keys())
195+
return all_routes
196+
197+
# Example usage and test handlers
198+
def get_users():
199+
return {"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}
200+
201+
def get_user(id: str):
202+
return {"user": {"id": int(id), "name": f"User {id}"}}
203+
204+
def create_user():
205+
return {"message": "User created successfully"}
206+
207+
def update_user(id: str):
208+
return {"message": f"User {id} updated successfully"}
209+
210+
def delete_user(id: str):
211+
return {"message": f"User {id} deleted successfully"}
212+
213+
# Example usage
214+
if __name__ == "__main__":
215+
router = Router()
216+
217+
# Register routes
218+
router.add_route('GET', '/users', get_users)
219+
router.add_route('GET', '/users/:id', get_user)
220+
router.add_route('POST', '/users', create_user)
221+
router.add_route('PUT', '/users/:id', update_user)
222+
router.add_route('DELETE', '/users/:id', delete_user)
223+
224+
# Test requests
225+
print(router.handle_request('GET', '/users'))
226+
print(router.handle_request('GET', '/users/123'))
227+
print(router.handle_request('POST', '/users'))
228+
print(router.handle_request('PUT', '/users/456'))
229+
print(router.handle_request('DELETE', '/users/789'))
230+
print(router.handle_request('GET', '/nonexistent'))
231+
print(router.handle_request('PATCH', '/users'))
232+
```
233+
234+
### Example Walkthrough
235+
**Example 1: Basic Route Registration and Matching**
236+
```python
237+
router = Router()
238+
239+
# Register routes
240+
router.add_route('GET', '/users', get_users_handler)
241+
router.add_route('GET', '/users/:id', get_user_handler)
242+
243+
# Handle requests
244+
response1 = router.handle_request('GET', '/users')
245+
# Returns: {'status_code': 200, 'message': 'OK', 'data': {...}}
246+
247+
response2 = router.handle_request('GET', '/users/123')
248+
# Returns: {'status_code': 200, 'message': 'OK', 'data': {'user': {'id': 123, 'name': 'User 123'}}}
249+
```
250+
251+
**Example 2: Error Handling**
252+
```python
253+
# 404 Not Found
254+
response = router.handle_request('GET', '/nonexistent')
255+
# Returns: {'status_code': 404, 'message': 'Not Found', 'data': None}
256+
257+
# 405 Method Not Allowed
258+
response = router.handle_request('PATCH', '/users')
259+
# Returns: {'status_code': 405, 'message': 'Method Not Allowed', 'data': None}
260+
```
261+
262+
**Example 3: Multiple Parameters**
263+
```python
264+
def get_user_posts(user_id: str, post_id: str):
265+
return {"user_id": user_id, "post_id": post_id, "content": "Post content"}
266+
267+
router.add_route('GET', '/users/:user_id/posts/:post_id', get_user_posts)
268+
269+
response = router.handle_request('GET', '/users/123/posts/456')
270+
# Returns: {'status_code': 200, 'message': 'OK', 'data': {'user_id': '123', 'post_id': '456', 'content': 'Post content'}}
271+
```
272+
273+
## Edge Cases & Validation
274+
- **Duplicate routes**: Should raise ValueError when registering duplicate routes
275+
- **Invalid HTTP methods**: Should handle case-insensitive method matching
276+
- **Empty paths**: Should handle root path `/` appropriately
277+
- **Special characters**: Should handle paths with special characters correctly
278+
- **Parameter validation**: Should extract parameters correctly from complex paths
279+
- **Handler exceptions**: Should catch and return 500 errors for handler exceptions
280+
- **Method not allowed**: Should return 405 when method exists but path doesn't
281+
- **Case sensitivity**: Should handle path matching case-sensitively
282+
- **Trailing slashes**: Should handle paths with and without trailing slashes consistently
283+
284+
## Related Problems
285+
- [208. Implement Trie (Prefix Tree)](https://leetcode.com/problems/implement-trie-prefix-tree/) - Tree-based routing
286+
- [211. Design Add and Search Words Data Structure](https://leetcode.com/problems/design-add-and-search-words-data-structure/) - Pattern matching
287+
- [676. Implement Magic Dictionary](https://leetcode.com/problems/implement-magic-dictionary/) - String matching with variations
288+
- [Design Twitter](https://leetcode.com/problems/design-twitter/) - System design with routing concepts

0 commit comments

Comments
 (0)