feat: add initial frontend and backend dependencies
This commit is contained in:
commit
3dedaf0899
|
|
@ -0,0 +1,28 @@
|
|||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.env
|
||||
*.egg-info/
|
||||
.coverage
|
||||
htmlcov/
|
||||
.pytest_cache/
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
.npm
|
||||
.eslintcache
|
||||
|
||||
# OS / IDE
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
thumbs.db
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Multi-stage Dockerfile for Algorithm Platform
|
||||
|
||||
# Stage 1: Build Frontend
|
||||
FROM node:18-alpine AS frontend-build
|
||||
WORKDIR /app/frontend
|
||||
COPY frontend/package*.json ./
|
||||
RUN npm install
|
||||
COPY frontend/ ./
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Setup Backend
|
||||
FROM python:3.11-slim AS backend
|
||||
WORKDIR /app/backend
|
||||
COPY backend/requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY backend/ ./
|
||||
|
||||
# Stage 3: Final - Nginx + Backend
|
||||
FROM nginx:alpine
|
||||
WORKDIR /app
|
||||
|
||||
# Install Python and dependencies in final stage
|
||||
RUN apk add --no-cache python3 py3-pip
|
||||
|
||||
# Copy backend
|
||||
COPY --from=backend /app/backend /app/backend
|
||||
COPY --from=backend /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
|
||||
|
||||
# Copy frontend build to Nginx
|
||||
COPY --from=frontend-build /app/frontend/dist /usr/share/nginx/html
|
||||
|
||||
# Copy Nginx configuration
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 80
|
||||
|
||||
# Start script
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
CMD ["/docker-entrypoint.sh"]
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# Algorithm Test Case Generation and Verification Platform
|
||||
|
||||
## Project Structure
|
||||
- `backend/`: FastAPI Python Backend
|
||||
- `frontend/`: React Vite Frontend
|
||||
|
||||
## Prerequisites
|
||||
- Python 3.8+
|
||||
- Node.js 16+
|
||||
|
||||
## Setup & Run
|
||||
|
||||
### Quick Start with Docker (Recommended)
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
Then access:
|
||||
- **Frontend**: http://localhost
|
||||
- **Backend API**: http://localhost:8000
|
||||
- **API Docs**: http://localhost:8000/docs
|
||||
|
||||
### Manual Setup
|
||||
|
||||
### 1. Backend
|
||||
```bash
|
||||
cd backend
|
||||
# Optional: Create virtual env
|
||||
# python -m venv venv
|
||||
# source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
Backend will run at `http://localhost:8000`
|
||||
|
||||
### 2. Frontend
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
Frontend will run at `http://localhost:5173`
|
||||
|
||||
## Features Implemented
|
||||
1. **Maximum Subarray Sum**:
|
||||
- Brute Force ($O(N^2)$)
|
||||
- Divide & Conquer ($O(N \log N)$)
|
||||
- Kadane's Algorithm ($O(N)$)
|
||||
- Automatic Random Case Generation
|
||||
- Performance Benchmarking & Visualization
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, List
|
||||
|
||||
class BaseProblem(ABC):
|
||||
"""
|
||||
Abstract base class for all algorithm problems.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def generate_case(self, **kwargs) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a random test case based on parameters.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def solve(self, input_data: Any, algorithm: str) -> Any:
|
||||
"""
|
||||
Solve the problem using the specified algorithm.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def verify(self, input_data: Any, result: Any) -> bool:
|
||||
"""
|
||||
Verify the correctness of the result (optional, or compare with standard answer).
|
||||
"""
|
||||
pass
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import time
|
||||
import functools
|
||||
from typing import Tuple, Any
|
||||
|
||||
def time_execution(func):
|
||||
"""
|
||||
Decorator to measure execution time of a function.
|
||||
Returns a tuple (result, execution_time_in_seconds).
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs) -> Tuple[Any, float]:
|
||||
start_time = time.perf_counter()
|
||||
result = func(*args, **kwargs)
|
||||
end_time = time.perf_counter()
|
||||
execution_time = end_time - start_time
|
||||
return result, execution_time
|
||||
return wrapper
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from routers import max_subarray_router
|
||||
|
||||
app = FastAPI(title="Algorithm Platform API")
|
||||
|
||||
# Allow CORS for frontend
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # In production, replace with specific frontend origin
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"message": "Welcome to Algorithm Verification Platform API"}
|
||||
|
||||
app.include_router(max_subarray_router.router)
|
||||
from routers import interval_scheduling_router
|
||||
app.include_router(interval_scheduling_router.router)
|
||||
from routers import n_queens_router
|
||||
app.include_router(n_queens_router.router)
|
||||
from routers import knapsack_router
|
||||
app.include_router(knapsack_router.router)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import random
|
||||
from typing import List, Tuple
|
||||
|
||||
def generate_interval_scheduling_case(size: int = 10, min_time: int = 0, max_time: int = 100) -> List[Tuple[int, int]]:
|
||||
"""
|
||||
Generates a list of random intervals (start, end).
|
||||
"""
|
||||
intervals = []
|
||||
for _ in range(size):
|
||||
start = random.randint(min_time, max_time - 1)
|
||||
# Ensure end > start
|
||||
duration = random.randint(1, (max_time - start) // 2 + 1 if (max_time - start) > 2 else 1)
|
||||
end = min(start + duration, max_time)
|
||||
if end <= start:
|
||||
end = start + 1
|
||||
intervals.append((start, end))
|
||||
return intervals
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
from typing import Any, Dict, List
|
||||
from common.base_problem import BaseProblem
|
||||
from problems.interval_scheduling.generator import generate_interval_scheduling_case
|
||||
from problems.interval_scheduling.solvers import solve_greedy, solve_brute_force
|
||||
from common.timer_utils import time_execution
|
||||
|
||||
class IntervalSchedulingProblem(BaseProblem):
|
||||
def generate_case(self, size: int = 10, min_time: int = 0, max_time: int = 100) -> Dict[str, Any]:
|
||||
intervals = generate_interval_scheduling_case(size, min_time, max_time)
|
||||
return {"intervals": intervals}
|
||||
|
||||
def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]:
|
||||
intervals = input_data.get("intervals", [])
|
||||
# Ensure intervals are tuples
|
||||
intervals = [tuple(x) for x in intervals]
|
||||
|
||||
result = None
|
||||
duration = 0.0
|
||||
|
||||
if algorithm == "greedy":
|
||||
result, duration = time_execution(solve_greedy)(intervals)
|
||||
elif algorithm == "brute_force":
|
||||
result, duration = time_execution(solve_brute_force)(intervals)
|
||||
else:
|
||||
raise ValueError(f"Unknown algorithm: {algorithm}")
|
||||
|
||||
return {
|
||||
"algorithm": algorithm,
|
||||
"result_count": len(result),
|
||||
"result_intervals": result,
|
||||
"time_seconds": duration
|
||||
}
|
||||
|
||||
def verify(self, input_data: Any, result: Any) -> bool:
|
||||
# Use Greedy as the source of truth (it is optimal for this problem)
|
||||
intervals = input_data.get("intervals", [])
|
||||
intervals = [tuple(x) for x in intervals]
|
||||
truth, _ = time_execution(solve_greedy)(intervals)
|
||||
return result.get("result_count") == len(truth)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
from typing import List, Tuple
|
||||
|
||||
def solve_greedy(intervals: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
|
||||
"""
|
||||
Greedy algorithm: Sort by end time, pick non-overlapping.
|
||||
O(N log N)
|
||||
"""
|
||||
if not intervals:
|
||||
return []
|
||||
|
||||
# Sort by end time
|
||||
sorted_intervals = sorted(intervals, key=lambda x: x[1])
|
||||
|
||||
selected = []
|
||||
last_end_time = -float('inf')
|
||||
|
||||
for start, end in sorted_intervals:
|
||||
if start >= last_end_time:
|
||||
selected.append((start, end))
|
||||
last_end_time = end
|
||||
|
||||
return selected
|
||||
|
||||
def solve_brute_force(intervals: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
|
||||
"""
|
||||
Brute Force algorithm: Try all combinations.
|
||||
O(2^N) - Only for small inputs!
|
||||
"""
|
||||
n = len(intervals)
|
||||
max_count = 0
|
||||
best_subset = []
|
||||
|
||||
# Helper to check if a subset of intervals is valid (non-overlapping)
|
||||
def is_valid(subset):
|
||||
sorted_subset = sorted(subset, key=lambda x: x[1])
|
||||
last_end = -float('inf')
|
||||
for start, end in sorted_subset:
|
||||
if start < last_end:
|
||||
return False
|
||||
last_end = end
|
||||
return True
|
||||
|
||||
# Iterate through all 2^N subsets (using bitmask)
|
||||
# Limit N to avoid hanging
|
||||
if n > 20:
|
||||
# Fallback or error for safety, though we handle this in router usually
|
||||
return solve_greedy(intervals) # Cheating slightly for safety if called directly with large N
|
||||
|
||||
for i in range(1 << n):
|
||||
subset = []
|
||||
for j in range(n):
|
||||
if (i >> j) & 1:
|
||||
subset.append(intervals[j])
|
||||
|
||||
if is_valid(subset):
|
||||
if len(subset) > max_count:
|
||||
max_count = len(subset)
|
||||
best_subset = subset
|
||||
|
||||
return best_subset
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import random
|
||||
from typing import List, Dict, Any
|
||||
|
||||
def generate_knapsack_case(num_items: int = 10, max_weight: int = 50, max_value: int = 100, capacity_ratio: float = 0.5) -> Dict[str, Any]:
|
||||
"""
|
||||
Generates a case for 0/1 Knapsack.
|
||||
"""
|
||||
items = []
|
||||
total_weight = 0
|
||||
for i in range(num_items):
|
||||
w = random.randint(1, max_weight)
|
||||
v = random.randint(1, max_value)
|
||||
items.append({"id": i, "weight": w, "value": v})
|
||||
total_weight += w
|
||||
|
||||
capacity = int(total_weight * capacity_ratio)
|
||||
|
||||
return {
|
||||
"items": items,
|
||||
"capacity": capacity
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
from typing import Any, Dict, List
|
||||
from common.base_problem import BaseProblem
|
||||
from problems.knapsack.generator import generate_knapsack_case
|
||||
from problems.knapsack.solvers import solve_dp, solve_backtracking
|
||||
from common.timer_utils import time_execution
|
||||
|
||||
class KnapsackProblem(BaseProblem):
|
||||
def generate_case(self, num_items: int = 10, max_weight: int = 50, max_value: int = 100) -> Dict[str, Any]:
|
||||
return generate_knapsack_case(num_items, max_weight, max_value)
|
||||
|
||||
def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]:
|
||||
items = input_data.get("items", [])
|
||||
capacity = input_data.get("capacity", 0)
|
||||
|
||||
result = None
|
||||
duration = 0.0
|
||||
|
||||
if algorithm == "dp":
|
||||
result, duration = time_execution(solve_dp)(items, capacity)
|
||||
elif algorithm == "backtracking":
|
||||
result, duration = time_execution(solve_backtracking)(items, capacity)
|
||||
else:
|
||||
raise ValueError(f"Unknown algorithm: {algorithm}")
|
||||
|
||||
return {
|
||||
"algorithm": algorithm,
|
||||
"max_value": result["max_value"],
|
||||
"selected_items": result["selected_items"],
|
||||
"time_seconds": duration
|
||||
}
|
||||
|
||||
def verify(self, input_data: Any, result: Any) -> bool:
|
||||
# Use DP as source of truth
|
||||
items = input_data.get("items", [])
|
||||
capacity = input_data.get("capacity", 0)
|
||||
truth, _ = time_execution(solve_dp)(items, capacity)
|
||||
return result["max_value"] == truth["max_value"]
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
from typing import List, Dict, Any, Tuple
|
||||
|
||||
def solve_dp(items: List[Dict[str, int]], capacity: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Dynamic Programming solution for 0/1 Knapsack.
|
||||
O(N * W)
|
||||
"""
|
||||
n = len(items)
|
||||
# dp[i][w] = max value using first i items with capacity w
|
||||
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
|
||||
|
||||
for i in range(1, n + 1):
|
||||
item = items[i-1]
|
||||
w, v = item["weight"], item["value"]
|
||||
for j in range(capacity + 1):
|
||||
if w <= j:
|
||||
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w] + v)
|
||||
else:
|
||||
dp[i][j] = dp[i-1][j]
|
||||
|
||||
max_value = dp[n][capacity]
|
||||
|
||||
# Backtrack to find selected items
|
||||
selected_indices = []
|
||||
w = capacity
|
||||
for i in range(n, 0, -1):
|
||||
if dp[i][w] != dp[i-1][w]:
|
||||
item = items[i-1]
|
||||
selected_indices.append(item["id"])
|
||||
w -= item["weight"]
|
||||
|
||||
return {
|
||||
"max_value": max_value,
|
||||
"selected_items": selected_indices
|
||||
}
|
||||
|
||||
def solve_backtracking(items: List[Dict[str, int]], capacity: int) -> Dict[str, Any]:
|
||||
"""
|
||||
Backtracking solution for 0/1 Knapsack.
|
||||
O(2^N)
|
||||
"""
|
||||
n = len(items)
|
||||
max_val = 0
|
||||
best_selection = []
|
||||
|
||||
def backtrack(index, current_weight, current_value, selection):
|
||||
nonlocal max_val, best_selection
|
||||
|
||||
if current_weight > capacity:
|
||||
return
|
||||
|
||||
if current_value > max_val:
|
||||
max_val = current_value
|
||||
best_selection = selection[:]
|
||||
|
||||
if index == n:
|
||||
return
|
||||
|
||||
# Include current item
|
||||
item = items[index]
|
||||
if current_weight + item["weight"] <= capacity:
|
||||
selection.append(item["id"])
|
||||
backtrack(index + 1, current_weight + item["weight"], current_value + item["value"], selection)
|
||||
selection.pop()
|
||||
|
||||
# Exclude current item
|
||||
backtrack(index + 1, current_weight, current_value, selection)
|
||||
|
||||
backtrack(0, 0, 0, [])
|
||||
return {
|
||||
"max_value": max_val,
|
||||
"selected_items": best_selection
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import random
|
||||
from typing import List, Dict
|
||||
|
||||
def generate_max_subarray_case(size: int = 100, min_val: int = -100, max_val: int = 100) -> List[int]:
|
||||
"""
|
||||
Generate a list of random integers.
|
||||
"""
|
||||
return [random.randint(min_val, max_val) for _ in range(size)]
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
from typing import Any, Dict, List
|
||||
from common.base_problem import BaseProblem
|
||||
from problems.max_subarray.generator import generate_max_subarray_case
|
||||
from problems.max_subarray.solvers import solve_brute_force, solve_divide_conquer, solve_kadane
|
||||
from common.timer_utils import time_execution
|
||||
|
||||
class MaxSubarrayProblem(BaseProblem):
|
||||
def generate_case(self, size: int = 100, min_val: int = -100, max_val: int = 100) -> Dict[str, Any]:
|
||||
arr = generate_max_subarray_case(size, min_val, max_val)
|
||||
return {"array": arr}
|
||||
|
||||
def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]:
|
||||
arr = input_data.get("array", [])
|
||||
result = None
|
||||
duration = 0.0
|
||||
|
||||
if algorithm == "brute_force":
|
||||
result, duration = time_execution(solve_brute_force)(arr)
|
||||
elif algorithm == "divide_conquer":
|
||||
result, duration = time_execution(solve_divide_conquer)(arr)
|
||||
elif algorithm == "kadane":
|
||||
result, duration = time_execution(solve_kadane)(arr)
|
||||
else:
|
||||
raise ValueError(f"Unknown algorithm: {algorithm}")
|
||||
|
||||
return {
|
||||
"algorithm": algorithm,
|
||||
"result": result,
|
||||
"time_seconds": duration
|
||||
}
|
||||
|
||||
def verify(self, input_data: Any, result: Any) -> bool:
|
||||
# Use Kadane as the source of truth
|
||||
truth, _ = time_execution(solve_kadane)(input_data.get("array", []))
|
||||
return result == truth
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
from typing import List, Tuple
|
||||
|
||||
# 1. Brute Force O(N^2)
|
||||
def solve_brute_force(arr: List[int]) -> int:
|
||||
max_sum = float('-inf')
|
||||
n = len(arr)
|
||||
for i in range(n):
|
||||
current_sum = 0
|
||||
for j in range(i, n):
|
||||
current_sum += arr[j]
|
||||
if current_sum > max_sum:
|
||||
max_sum = current_sum
|
||||
return max_sum
|
||||
|
||||
# 2. Divide and Conquer O(N log N)
|
||||
def solve_divide_conquer(arr: List[int]) -> int:
|
||||
def max_crossing_sum(arr, low, mid, high):
|
||||
left_sum = float('-inf')
|
||||
curr_sum = 0
|
||||
for i in range(mid, low - 1, -1):
|
||||
curr_sum += arr[i]
|
||||
if curr_sum > left_sum:
|
||||
left_sum = curr_sum
|
||||
|
||||
right_sum = float('-inf')
|
||||
curr_sum = 0
|
||||
for i in range(mid + 1, high + 1):
|
||||
curr_sum += arr[i]
|
||||
if curr_sum > right_sum:
|
||||
right_sum = curr_sum
|
||||
|
||||
return left_sum + right_sum
|
||||
|
||||
def solve_recursive(arr, low, high):
|
||||
if low == high:
|
||||
return arr[low]
|
||||
|
||||
mid = (low + high) // 2
|
||||
|
||||
return max(
|
||||
solve_recursive(arr, low, mid),
|
||||
solve_recursive(arr, mid + 1, high),
|
||||
max_crossing_sum(arr, low, mid, high)
|
||||
)
|
||||
|
||||
if not arr:
|
||||
return 0
|
||||
return solve_recursive(arr, 0, len(arr) - 1)
|
||||
|
||||
# 3. Dynamic Programming (Kadane's Algorithm) O(N)
|
||||
def solve_kadane(arr: List[int]) -> int:
|
||||
if not arr:
|
||||
return 0
|
||||
max_so_far = arr[0]
|
||||
curr_max = arr[0]
|
||||
|
||||
for i in range(1, len(arr)):
|
||||
curr_max = max(arr[i], curr_max + arr[i])
|
||||
max_so_far = max(max_so_far, curr_max)
|
||||
|
||||
return max_so_far
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
from typing import Dict, Any
|
||||
|
||||
def generate_n_queens_case(n: int = 8) -> Dict[str, int]:
|
||||
"""
|
||||
Generates a case for N-Queens.
|
||||
Actually just returns N since the problem is defined by the board size.
|
||||
"""
|
||||
return {"n": n}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
from typing import Any, Dict, List
|
||||
from common.base_problem import BaseProblem
|
||||
from problems.n_queens.generator import generate_n_queens_case
|
||||
from problems.n_queens.solvers import solve_backtracking
|
||||
from common.timer_utils import time_execution
|
||||
|
||||
class NQueensProblem(BaseProblem):
|
||||
def generate_case(self, n: int = 8) -> Dict[str, Any]:
|
||||
return generate_n_queens_case(n)
|
||||
|
||||
def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]:
|
||||
n = input_data.get("n", 8)
|
||||
result = None
|
||||
duration = 0.0
|
||||
|
||||
if algorithm == "backtracking":
|
||||
# For visualization, we usually just want one solution,
|
||||
# but for benchmarking maybe we want to count all?
|
||||
# Let's default to finding ONE solution for "solve" to be fast enough for UI
|
||||
# If N is small (< 12), we can find all.
|
||||
find_all = n < 10
|
||||
result, duration = time_execution(solve_backtracking)(n, find_all)
|
||||
else:
|
||||
raise ValueError(f"Unknown algorithm: {algorithm}")
|
||||
|
||||
return {
|
||||
"algorithm": algorithm,
|
||||
"solution_count": len(result),
|
||||
"first_solution": result[0] if result else None,
|
||||
"time_seconds": duration
|
||||
}
|
||||
|
||||
def verify(self, input_data: Any, result: Any) -> bool:
|
||||
# Verification is tricky without a ground truth.
|
||||
# We can check if the returned solution is valid.
|
||||
sol = result.get("first_solution")
|
||||
if not sol:
|
||||
return True # No solution found (could be valid for some N, though N>=4 always has sol)
|
||||
|
||||
n = len(sol)
|
||||
for r in range(n):
|
||||
c = sol[r]
|
||||
# Check conflicts
|
||||
for prev_r in range(r):
|
||||
prev_c = sol[prev_r]
|
||||
if c == prev_c or abs(r - prev_r) == abs(c - prev_c):
|
||||
return False
|
||||
return True
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
from typing import List, Tuple, Optional
|
||||
|
||||
def solve_backtracking(n: int, find_all: bool = False) -> List[List[int]]:
|
||||
"""
|
||||
Backtracking algorithm for N-Queens.
|
||||
Returns a list of solutions. Each solution is a list of column indices for each row.
|
||||
"""
|
||||
solutions = []
|
||||
board = [-1] * n # board[row] = col
|
||||
cols = [False] * n
|
||||
diag1 = [False] * (2 * n - 1) # row + col
|
||||
diag2 = [False] * (2 * n - 1) # row - col + (n - 1)
|
||||
|
||||
def backtrack(row: int):
|
||||
if row == n:
|
||||
solutions.append(board[:])
|
||||
return True
|
||||
|
||||
for col in range(n):
|
||||
if not cols[col] and not diag1[row + col] and not diag2[row - col + n - 1]:
|
||||
board[row] = col
|
||||
cols[col] = True
|
||||
diag1[row + col] = True
|
||||
diag2[row - col + n - 1] = True
|
||||
|
||||
if backtrack(row + 1):
|
||||
if not find_all:
|
||||
return True
|
||||
|
||||
# Backtrack
|
||||
cols[col] = False
|
||||
diag1[row + col] = False
|
||||
diag2[row - col + n - 1] = False
|
||||
|
||||
return False
|
||||
|
||||
backtrack(0)
|
||||
return solutions
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
fastapi
|
||||
uvicorn
|
||||
matplotlib
|
||||
pandas
|
||||
numpy
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional, Dict, Any, Tuple
|
||||
from problems.interval_scheduling.problem import IntervalSchedulingProblem
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/interval_scheduling",
|
||||
tags=["interval_scheduling"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
problem_solver = IntervalSchedulingProblem()
|
||||
|
||||
class GenerateRequest(BaseModel):
|
||||
size: int = 10
|
||||
min_time: int = 0
|
||||
max_time: int = 100
|
||||
|
||||
class SolveRequest(BaseModel):
|
||||
intervals: List[Tuple[int, int]]
|
||||
algorithms: List[str] = ["greedy", "brute_force"]
|
||||
|
||||
class BenchmarkRequest(BaseModel):
|
||||
sizes: List[int] = [5, 10, 15, 20]
|
||||
algorithms: List[str] = ["greedy", "brute_force"]
|
||||
|
||||
@router.post("/generate")
|
||||
async def generate_case(req: GenerateRequest):
|
||||
return problem_solver.generate_case(size=req.size, min_time=req.min_time, max_time=req.max_time)
|
||||
|
||||
@router.post("/solve")
|
||||
async def solve_case(req: SolveRequest):
|
||||
input_data = {"intervals": req.intervals}
|
||||
results = []
|
||||
for algo in req.algorithms:
|
||||
try:
|
||||
# Skip Brute Force for large inputs to avoid timeout
|
||||
if len(req.intervals) > 22 and algo == "brute_force":
|
||||
results.append({
|
||||
"algorithm": algo,
|
||||
"error": "Input too large for Brute Force (limit 22)",
|
||||
"skipped": True
|
||||
})
|
||||
continue
|
||||
|
||||
res = problem_solver.solve(input_data, algo)
|
||||
results.append(res)
|
||||
except ValueError as e:
|
||||
results.append({"algorithm": algo, "error": str(e)})
|
||||
return results
|
||||
|
||||
@router.post("/benchmark")
|
||||
async def benchmark(req: BenchmarkRequest):
|
||||
benchmark_results = []
|
||||
for size in req.sizes:
|
||||
input_data = problem_solver.generate_case(size=size)
|
||||
|
||||
size_result = {"size": size, "algorithms": []}
|
||||
for algo in req.algorithms:
|
||||
try:
|
||||
if size > 22 and algo == "brute_force":
|
||||
size_result["algorithms"].append({
|
||||
"algorithm": algo,
|
||||
"time_seconds": None,
|
||||
"skipped": True
|
||||
})
|
||||
continue
|
||||
|
||||
res = problem_solver.solve(input_data, algo)
|
||||
size_result["algorithms"].append(res)
|
||||
except Exception as e:
|
||||
size_result["algorithms"].append({"algorithm": algo, "error": str(e)})
|
||||
|
||||
benchmark_results.append(size_result)
|
||||
return benchmark_results
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional, Dict, Any
|
||||
from problems.knapsack.problem import KnapsackProblem
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/knapsack",
|
||||
tags=["knapsack"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
problem_solver = KnapsackProblem()
|
||||
|
||||
class GenerateRequest(BaseModel):
|
||||
num_items: int = 10
|
||||
max_weight: int = 50
|
||||
max_value: int = 100
|
||||
|
||||
class SolveRequest(BaseModel):
|
||||
items: List[Dict[str, int]]
|
||||
capacity: int
|
||||
algorithms: List[str] = ["dp", "backtracking"]
|
||||
|
||||
class BenchmarkRequest(BaseModel):
|
||||
sizes: List[int] = [10, 20, 30]
|
||||
algorithms: List[str] = ["dp", "backtracking"]
|
||||
|
||||
@router.post("/generate")
|
||||
async def generate_case(req: GenerateRequest):
|
||||
return problem_solver.generate_case(num_items=req.num_items, max_weight=req.max_weight, max_value=req.max_value)
|
||||
|
||||
@router.post("/solve")
|
||||
async def solve_case(req: SolveRequest):
|
||||
input_data = {"items": req.items, "capacity": req.capacity}
|
||||
results = []
|
||||
for algo in req.algorithms:
|
||||
try:
|
||||
# Skip Backtracking for large inputs
|
||||
if len(req.items) > 22 and algo == "backtracking":
|
||||
results.append({
|
||||
"algorithm": algo,
|
||||
"error": "Input too large for Backtracking (limit 22)",
|
||||
"skipped": True
|
||||
})
|
||||
continue
|
||||
|
||||
res = problem_solver.solve(input_data, algo)
|
||||
results.append(res)
|
||||
except ValueError as e:
|
||||
results.append({"algorithm": algo, "error": str(e)})
|
||||
return results
|
||||
|
||||
@router.post("/benchmark")
|
||||
async def benchmark(req: BenchmarkRequest):
|
||||
benchmark_results = []
|
||||
for size in req.sizes:
|
||||
input_data = problem_solver.generate_case(num_items=size)
|
||||
|
||||
size_result = {"size": size, "algorithms": []}
|
||||
for algo in req.algorithms:
|
||||
try:
|
||||
if size > 22 and algo == "backtracking":
|
||||
size_result["algorithms"].append({
|
||||
"algorithm": algo,
|
||||
"time_seconds": None,
|
||||
"skipped": True
|
||||
})
|
||||
continue
|
||||
|
||||
res = problem_solver.solve(input_data, algo)
|
||||
size_result["algorithms"].append(res)
|
||||
except Exception as e:
|
||||
size_result["algorithms"].append({"algorithm": algo, "error": str(e)})
|
||||
|
||||
benchmark_results.append(size_result)
|
||||
return benchmark_results
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional, Dict, Any
|
||||
from problems.max_subarray.problem import MaxSubarrayProblem
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/max_subarray",
|
||||
tags=["max_subarray"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
problem_solver = MaxSubarrayProblem()
|
||||
|
||||
class GenerateRequest(BaseModel):
|
||||
size: int = 100
|
||||
min_val: int = -100
|
||||
max_val: int = 100
|
||||
|
||||
class SolveRequest(BaseModel):
|
||||
array: List[int]
|
||||
algorithms: List[str] = ["kadane", "divide_conquer", "brute_force"]
|
||||
|
||||
class BenchmarkRequest(BaseModel):
|
||||
sizes: List[int] = [10, 50, 100, 500, 1000]
|
||||
algorithms: List[str] = ["kadane", "divide_conquer", "brute_force"]
|
||||
|
||||
@router.post("/generate")
|
||||
async def generate_case(req: GenerateRequest):
|
||||
return problem_solver.generate_case(size=req.size, min_val=req.min_val, max_val=req.max_val)
|
||||
|
||||
@router.post("/solve")
|
||||
async def solve_case(req: SolveRequest):
|
||||
input_data = {"array": req.array}
|
||||
results = []
|
||||
for algo in req.algorithms:
|
||||
try:
|
||||
res = problem_solver.solve(input_data, algo)
|
||||
results.append(res)
|
||||
except ValueError as e:
|
||||
results.append({"algorithm": algo, "error": str(e)})
|
||||
return results
|
||||
|
||||
@router.post("/benchmark")
|
||||
async def benchmark(req: BenchmarkRequest):
|
||||
benchmark_results = []
|
||||
for size in req.sizes:
|
||||
# Generate a random case for this size
|
||||
# To reduce noise, we might want to run multiple times and average,
|
||||
# but for now single run per size is fine for demonstration
|
||||
input_data = problem_solver.generate_case(size=size)
|
||||
|
||||
size_result = {"size": size, "algorithms": []}
|
||||
for algo in req.algorithms:
|
||||
try:
|
||||
# Skip O(N^2) for very large inputs to avoid timeout
|
||||
if size > 5000 and algo == "brute_force":
|
||||
size_result["algorithms"].append({
|
||||
"algorithm": algo,
|
||||
"time_seconds": None,
|
||||
"skipped": True
|
||||
})
|
||||
continue
|
||||
|
||||
res = problem_solver.solve(input_data, algo)
|
||||
size_result["algorithms"].append(res)
|
||||
except Exception as e:
|
||||
size_result["algorithms"].append({"algorithm": algo, "error": str(e)})
|
||||
|
||||
benchmark_results.append(size_result)
|
||||
return benchmark_results
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional, Dict, Any
|
||||
from problems.n_queens.problem import NQueensProblem
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/n_queens",
|
||||
tags=["n_queens"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
problem_solver = NQueensProblem()
|
||||
|
||||
class GenerateRequest(BaseModel):
|
||||
n: int = 8
|
||||
|
||||
class SolveRequest(BaseModel):
|
||||
n: int
|
||||
algorithms: List[str] = ["backtracking"]
|
||||
|
||||
class BenchmarkRequest(BaseModel):
|
||||
sizes: List[int] = [4, 8, 10, 12]
|
||||
algorithms: List[str] = ["backtracking"]
|
||||
|
||||
@router.post("/generate")
|
||||
async def generate_case(req: GenerateRequest):
|
||||
return problem_solver.generate_case(n=req.n)
|
||||
|
||||
@router.post("/solve")
|
||||
async def solve_case(req: SolveRequest):
|
||||
input_data = {"n": req.n}
|
||||
results = []
|
||||
for algo in req.algorithms:
|
||||
try:
|
||||
res = problem_solver.solve(input_data, algo)
|
||||
results.append(res)
|
||||
except ValueError as e:
|
||||
results.append({"algorithm": algo, "error": str(e)})
|
||||
return results
|
||||
|
||||
@router.post("/benchmark")
|
||||
async def benchmark(req: BenchmarkRequest):
|
||||
benchmark_results = []
|
||||
for size in req.sizes:
|
||||
input_data = {"n": size}
|
||||
|
||||
size_result = {"size": size, "algorithms": []}
|
||||
for algo in req.algorithms:
|
||||
try:
|
||||
# Skip large N for backtracking if finding all solutions (though our solve defaults to one for large N)
|
||||
# But for benchmark we might want to be careful.
|
||||
if size > 14 and algo == "backtracking":
|
||||
size_result["algorithms"].append({
|
||||
"algorithm": algo,
|
||||
"time_seconds": None,
|
||||
"skipped": True
|
||||
})
|
||||
continue
|
||||
|
||||
res = problem_solver.solve(input_data, algo)
|
||||
size_result["algorithms"].append(res)
|
||||
except Exception as e:
|
||||
size_result["algorithms"].append({"algorithm": algo, "error": str(e)})
|
||||
|
||||
benchmark_results.append(size_result)
|
||||
return benchmark_results
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import sys
|
||||
import os
|
||||
import random
|
||||
|
||||
# Add backend to path
|
||||
sys.path.append(os.path.abspath("/Users/hissin/测试/25-cxsj-final/backend"))
|
||||
|
||||
from problems.max_subarray.problem import MaxSubarrayProblem
|
||||
from problems.interval_scheduling.problem import IntervalSchedulingProblem
|
||||
from problems.n_queens.problem import NQueensProblem
|
||||
from problems.knapsack.problem import KnapsackProblem
|
||||
|
||||
def test_max_subarray():
|
||||
print("\nTesting Max Subarray...")
|
||||
prob = MaxSubarrayProblem()
|
||||
case = prob.generate_case(size=10)
|
||||
res = prob.solve(case, "kadane")
|
||||
print(f"Max Subarray Result: {res['result']}")
|
||||
assert prob.verify(case, res['result'])
|
||||
print("Max Subarray Passed!")
|
||||
|
||||
def test_interval_scheduling():
|
||||
print("\nTesting Interval Scheduling...")
|
||||
prob = IntervalSchedulingProblem()
|
||||
case = prob.generate_case(size=10)
|
||||
res = prob.solve(case, "greedy")
|
||||
print(f"Interval Scheduling Count: {res['result_count']}")
|
||||
assert prob.verify(case, res)
|
||||
print("Interval Scheduling Passed!")
|
||||
|
||||
def test_n_queens():
|
||||
print("\nTesting N-Queens...")
|
||||
prob = NQueensProblem()
|
||||
case = prob.generate_case(n=8)
|
||||
res = prob.solve(case, "backtracking")
|
||||
print(f"N-Queens (N=8) Solutions: {res['solution_count']}")
|
||||
assert res['solution_count'] == 92
|
||||
print("N-Queens Passed!")
|
||||
|
||||
def test_knapsack():
|
||||
print("\nTesting Knapsack...")
|
||||
prob = KnapsackProblem()
|
||||
case = prob.generate_case(num_items=10, max_weight=20, max_value=100)
|
||||
res_dp = prob.solve(case, "dp")
|
||||
res_back = prob.solve(case, "backtracking")
|
||||
|
||||
print(f"Knapsack DP Value: {res_dp['max_value']}")
|
||||
print(f"Knapsack Backtracking Value: {res_back['max_value']}")
|
||||
|
||||
assert res_dp['max_value'] == res_back['max_value']
|
||||
assert prob.verify(case, res_dp)
|
||||
print("Knapsack Passed!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_max_subarray()
|
||||
test_interval_scheduling()
|
||||
test_n_queens()
|
||||
test_knapsack()
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
# Add backend to path
|
||||
sys.path.append(os.path.abspath("/Users/hissin/测试/25-cxsj-final/backend"))
|
||||
|
||||
from problems.interval_scheduling.generator import generate_interval_scheduling_case
|
||||
from problems.interval_scheduling.solvers import solve_greedy, solve_brute_force
|
||||
from problems.interval_scheduling.problem import IntervalSchedulingProblem
|
||||
|
||||
def test_generator():
|
||||
print("Testing Generator...")
|
||||
intervals = generate_interval_scheduling_case(size=10, min_time=0, max_time=100)
|
||||
print(f"Generated {len(intervals)} intervals: {intervals}")
|
||||
assert len(intervals) == 10
|
||||
for start, end in intervals:
|
||||
assert start < end
|
||||
assert start >= 0
|
||||
assert end <= 100
|
||||
print("Generator Passed!")
|
||||
|
||||
def test_solvers():
|
||||
print("\nTesting Solvers...")
|
||||
# Known case
|
||||
intervals = [(1, 3), (2, 4), (3, 5)]
|
||||
# Greedy should pick (1,3) and (3,5) -> count 2
|
||||
greedy_res = solve_greedy(intervals)
|
||||
print(f"Greedy result: {greedy_res}")
|
||||
assert len(greedy_res) == 2
|
||||
|
||||
brute_res = solve_brute_force(intervals)
|
||||
print(f"Brute Force result: {brute_res}")
|
||||
assert len(brute_res) == 2
|
||||
|
||||
# Complex case
|
||||
intervals = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9), (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)]
|
||||
greedy_res = solve_greedy(intervals)
|
||||
brute_res = solve_brute_force(intervals)
|
||||
print(f"Complex Greedy: {len(greedy_res)}")
|
||||
print(f"Complex Brute: {len(brute_res)}")
|
||||
assert len(greedy_res) == len(brute_res)
|
||||
print("Solvers Passed!")
|
||||
|
||||
def test_problem_class():
|
||||
print("\nTesting Problem Class...")
|
||||
prob = IntervalSchedulingProblem()
|
||||
case = prob.generate_case(size=5)
|
||||
res_greedy = prob.solve(case, "greedy")
|
||||
res_brute = prob.solve(case, "brute_force")
|
||||
|
||||
print(f"Class Greedy: {res_greedy}")
|
||||
print(f"Class Brute: {res_brute}")
|
||||
|
||||
assert prob.verify(case, res_greedy)
|
||||
assert res_greedy["result_count"] == res_brute["result_count"]
|
||||
print("Problem Class Passed!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_generator()
|
||||
test_solvers()
|
||||
test_problem_class()
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# 算法测试用例生成与自我验证平台 - 设计文档
|
||||
|
||||
## 1. 项目概述
|
||||
本项目旨在构建一个集“自动出题”、“多算法求解”、“性能分析”于一体的算法学习与验证平台。通过针对特定算法问题生成随机测试数据,并使用不同复杂度的算法进行求解,验证结果一致性并对比运行效率。项目包含一个现代化 Web 前端界面,用于交互式地配置参数与展示图表。
|
||||
|
||||
## 2. 系统架构设计
|
||||
系统采用 **前后端分离** 架构:
|
||||
|
||||
### 2.1 核心模块
|
||||
#### 后端 (Backend)
|
||||
1. **Problem Interface (问题接口)**
|
||||
* 定义问题的通用接口,包括 `generate_case()` (生成用例), `solve()` (求解), `verify()` (验证)。
|
||||
2. **Solver Engine (求解引擎)**
|
||||
* 针对每个问题,实现多种算法策略(暴力、分治、DP、贪心等)。
|
||||
3. **Performance Analyzer (性能分析器)**
|
||||
* 执行基准测试,验证正确性,统计运行时间。
|
||||
4. **API Layer (API 层)**
|
||||
* 基于 **FastAPI** 提供 RESTful 接口,处理前端的配置请求并返回计算结果。
|
||||
|
||||
#### 前端 (Frontend)
|
||||
1. **Control Panel (控制面板)**
|
||||
* 选择算法问题(如“最大子数组和”、“0/1背包”)。
|
||||
* 配置生成参数(数据规模、数值范围、稀疏度等)。
|
||||
2. **Visualization (可视化展示)**
|
||||
* 使用图表库展示不同算法的时间复杂度对比曲线。
|
||||
* 展示具体的测试用例数据与解题结果(用于教学演示)。
|
||||
|
||||
### 2.2 技术栈
|
||||
* **后端**: Python 3.x, FastAPI (Web框架), Matplotlib/Pandas (数据分析)
|
||||
* **前端**: React, TailwindCSS (样式), Recharts (图表可视化), Axios (网络请求)
|
||||
|
||||
## 3. 选定算法问题
|
||||
|
||||
### 问题一:最大子数组和 (Maximum Subarray Sum)
|
||||
**描述**: 给定一个整数数组,找到一个具有最大和的连续子数组。
|
||||
**测试用例生成**:
|
||||
* 随机生成长度为 $N$ 的整数数组。
|
||||
* 数值范围可配置(包含正负数)。
|
||||
**算法实现**:
|
||||
1. **暴力枚举 (Brute Force)**: 双重循环枚举所有子数组,时间复杂度 $O(N^2)$。
|
||||
2. **分治算法 (Divide and Conquer)**: 递归将数组分为左右两半,最大子数组可能在左、右或跨越中点,时间复杂度 $O(N \log N)$。
|
||||
3. **动态规划 (Kadane's Algorithm)**: 线性扫描,时间复杂度 $O(N)$。
|
||||
|
||||
### 问题二:0/1 背包问题 (0/1 Knapsack Problem)
|
||||
**描述**: 给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,选择其中若干个(也即每种物品可以选0个或1个),使得物品的总价值最高。
|
||||
**测试用例生成**:
|
||||
* 随机生成 $N$ 个物品,每个物品具有重量 $w$ 和价值 $v$。
|
||||
* 随机生成背包容量 $C$。
|
||||
**算法实现**:
|
||||
1. **回溯法 (Backtracking)**: 递归枚举所有可能的选择情况,时间复杂度 $O(2^N)$。
|
||||
2. **动态规划 (Dynamic Programming)**: 使用二维或一维数组记录状态,时间复杂度 $O(N \cdot C)$。
|
||||
3. **贪心算法 (Greedy, 近似解/对比)**: 按价值密度排序选择(注意:0/1背包贪心法通常不能得到最优解,可作为对比项展示“错误”或“近似”的算法,或者用于对比部分背包问题)。*注:本项目中为了验证正确性,主要对比回溯与DP,贪心可作为错误示范分析。*
|
||||
|
||||
## 4. 目录结构规划
|
||||
```
|
||||
algorithm_platform/
|
||||
├── README.md
|
||||
├── DESIGN_DOCUMENT.md
|
||||
├── backend/ # 后端代码
|
||||
│ ├── main.py # FastAPI 入口
|
||||
│ ├── common/ # 通用工具
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── base_problem.py
|
||||
│ │ └── timer_utils.py
|
||||
│ ├── problems/ # 算法实现模块
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── max_subarray/
|
||||
│ │ └── knapsack/
|
||||
│ └── routers/ # API 路由
|
||||
└── frontend/ # 前端代码
|
||||
├── src/
|
||||
│ ├── components/ # UI 组件
|
||||
│ ├── pages/ # 页面
|
||||
│ └── services/ # API 调用
|
||||
├── package.json
|
||||
└── vite.config.js
|
||||
```
|
||||
|
||||
## 5. 开发计划
|
||||
1. **第一阶段**: 搭建后端核心骨架 (FastAPI + Base Classes)。
|
||||
2. **第二阶段**: 实现“最大子数组和”算法逻辑与 API。
|
||||
3. **第三阶段**: 搭建 React 前端框架,实现基础交互与图表组件。
|
||||
4. **第四阶段**: 实现“0/1背包”算法逻辑,并接入前端。
|
||||
5. **第五阶段**: 优化 UI/UX,完善性能对比的可视化报告。
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Algorithm Platform</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "algorithm-platform-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"lucide-react": "^0.292.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"recharts": "^2.9.0",
|
||||
"clsx": "^2.0.0",
|
||||
"tailwind-merge": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,72 @@
|
|||
import React, { useState } from 'react';
|
||||
import MaxSubarray from './components/MaxSubarray';
|
||||
import IntervalScheduling from './components/IntervalScheduling';
|
||||
import NQueens from './components/NQueens';
|
||||
import Knapsack from './components/Knapsack';
|
||||
|
||||
function App() {
|
||||
const [activeTab, setActiveTab] = useState('max_subarray');
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<nav className="bg-white shadow-sm">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex items-center">
|
||||
<span className="text-xl font-bold text-gray-800">算法验证平台</span>
|
||||
<div className="ml-10 flex items-baseline space-x-4">
|
||||
<button
|
||||
onClick={() => setActiveTab('max_subarray')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'max_subarray'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
最大子数组和
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('interval_scheduling')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'interval_scheduling'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
区间调度
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('n_queens')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'n_queens'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
N皇后
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('knapsack')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'knapsack'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
0/1 背包
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div className="px-4 py-6 sm:px-0">
|
||||
{activeTab === 'max_subarray' && <MaxSubarray />}
|
||||
{activeTab === 'interval_scheduling' && <IntervalScheduling />}
|
||||
{activeTab === 'n_queens' && <NQueens />}
|
||||
{activeTab === 'knapsack' && <Knapsack />}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import axios from 'axios';
|
||||
|
||||
const API_URL = 'http://localhost:8000';
|
||||
|
||||
export const generateCase = async (size, minVal, maxVal) => {
|
||||
const response = await axios.post(`${API_URL}/max_subarray/generate`, {
|
||||
size,
|
||||
min_val: minVal,
|
||||
max_val: maxVal
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const solveCase = async (array, algorithms) => {
|
||||
const response = await axios.post(`${API_URL}/max_subarray/solve`, {
|
||||
array,
|
||||
algorithms
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const runBenchmark = async (sizes, algorithms) => {
|
||||
const response = await axios.post(`${API_URL}/max_subarray/benchmark`, {
|
||||
sizes,
|
||||
algorithms
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Interval Scheduling API
|
||||
export const generateIntervalCase = async (size, minTime, maxTime) => {
|
||||
const response = await axios.post(`${API_URL}/interval_scheduling/generate`, {
|
||||
size,
|
||||
min_time: minTime,
|
||||
max_time: maxTime
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const solveIntervalCase = async (intervals, algorithms) => {
|
||||
const response = await axios.post(`${API_URL}/interval_scheduling/solve`, {
|
||||
intervals,
|
||||
algorithms
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const runIntervalBenchmark = async (sizes, algorithms) => {
|
||||
const response = await axios.post(`${API_URL}/interval_scheduling/benchmark`, {
|
||||
sizes,
|
||||
algorithms
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// N-Queens API
|
||||
export const generateNQueensCase = async (n) => {
|
||||
const response = await axios.post(`${API_URL}/n_queens/generate`, { n });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const solveNQueensCase = async (n, algorithms) => {
|
||||
const response = await axios.post(`${API_URL}/n_queens/solve`, {
|
||||
n,
|
||||
algorithms
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const runNQueensBenchmark = async (sizes, algorithms) => {
|
||||
const response = await axios.post(`${API_URL}/n_queens/benchmark`, {
|
||||
sizes,
|
||||
algorithms
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Knapsack API
|
||||
export const generateKnapsackCase = async (numItems, maxWeight, maxValue) => {
|
||||
const response = await axios.post(`${API_URL}/knapsack/generate`, {
|
||||
num_items: numItems,
|
||||
max_weight: maxWeight,
|
||||
max_value: maxValue
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const solveKnapsackCase = async (items, capacity, algorithms) => {
|
||||
const response = await axios.post(`${API_URL}/knapsack/solve`, {
|
||||
items,
|
||||
capacity,
|
||||
algorithms
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const runKnapsackBenchmark = async (sizes, algorithms) => {
|
||||
const response = await axios.post(`${API_URL}/knapsack/benchmark`, {
|
||||
sizes,
|
||||
algorithms
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
import React, { useState } from 'react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
import { generateIntervalCase, solveIntervalCase, runIntervalBenchmark } from '../api';
|
||||
|
||||
const IntervalScheduling = () => {
|
||||
const [params, setParams] = useState({ size: 10, min: 0, max: 100 });
|
||||
const [currentCase, setCurrentCase] = useState(null);
|
||||
const [solveResults, setSolveResults] = useState(null);
|
||||
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Algorithm selection state
|
||||
const [selectedAlgorithms, setSelectedAlgorithms] = useState({
|
||||
greedy: true,
|
||||
brute_force: true
|
||||
});
|
||||
|
||||
const algorithmsList = [
|
||||
{ id: 'greedy', name: '贪心算法 (Greedy) O(N log N)' },
|
||||
{ id: 'brute_force', name: '暴力求解 (Brute Force) O(2^N)' }
|
||||
];
|
||||
|
||||
const getSelectedAlgoKeys = () => {
|
||||
return Object.keys(selectedAlgorithms).filter(k => selectedAlgorithms[k]);
|
||||
};
|
||||
|
||||
const handleGenerate = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await generateIntervalCase(params.size, params.min, params.max);
|
||||
setCurrentCase(data.intervals);
|
||||
setSolveResults(null);
|
||||
} catch (error) {
|
||||
console.error("Error generating case:", error);
|
||||
alert("生成测试用例失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleSolve = async () => {
|
||||
if (!currentCase) return;
|
||||
const algos = getSelectedAlgoKeys();
|
||||
if (algos.length === 0) {
|
||||
alert("请至少选择一种算法");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const results = await solveIntervalCase(currentCase, algos);
|
||||
setSolveResults(results);
|
||||
} catch (error) {
|
||||
console.error("Error solving case:", error);
|
||||
alert("求解失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleBenchmark = async () => {
|
||||
const algos = getSelectedAlgoKeys();
|
||||
if (algos.length === 0) {
|
||||
alert("请至少选择一种算法");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// Smaller sizes for Brute Force safety
|
||||
const sizes = [5, 10, 15, 20, 22];
|
||||
const results = await runIntervalBenchmark(sizes, algos);
|
||||
|
||||
// Transform data for Recharts
|
||||
const chartData = results.map(item => {
|
||||
const point = { size: item.size };
|
||||
item.algorithms.forEach(algo => {
|
||||
if (algo.time_seconds !== undefined && algo.time_seconds !== null) {
|
||||
point[algo.algorithm] = algo.time_seconds;
|
||||
}
|
||||
});
|
||||
return point;
|
||||
});
|
||||
|
||||
setBenchmarkResults(chartData);
|
||||
} catch (error) {
|
||||
console.error("Error running benchmark:", error);
|
||||
alert("性能测试失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const toggleAlgorithm = (algoId) => {
|
||||
setSelectedAlgorithms(prev => ({
|
||||
...prev,
|
||||
[algoId]: !prev[algoId]
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-8">
|
||||
<h1 className="text-3xl font-bold text-gray-800">区间调度问题 (Interval Scheduling)</h1>
|
||||
<p className="text-gray-600">目标:在给定的一组区间中,选择尽可能多的互不重叠的区间。</p>
|
||||
|
||||
{/* Algorithm Selection */}
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-xl font-semibold mb-4">算法选择</h2>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{algorithmsList.map(algo => (
|
||||
<label key={algo.id} className="flex items-center space-x-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedAlgorithms[algo.id]}
|
||||
onChange={() => toggleAlgorithm(algo.id)}
|
||||
className="form-checkbox h-5 w-5 text-blue-600"
|
||||
/>
|
||||
<span className="text-gray-700">{algo.name}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section 1: Generate & Solve */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-xl font-semibold mb-4">1. 生成测试用例</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">区间数量 (N)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.size}
|
||||
onChange={(e) => setParams({ ...params, size: parseInt(e.target.value) })}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">最小时间</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.min}
|
||||
onChange={(e) => setParams({ ...params, min: parseInt(e.target.value) })}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">最大时间</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.max}
|
||||
onChange={(e) => setParams({ ...params, max: parseInt(e.target.value) })}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleGenerate}
|
||||
disabled={loading}
|
||||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400"
|
||||
>
|
||||
{loading ? '处理中...' : '生成测试用例'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{currentCase && (
|
||||
<div className="mt-4">
|
||||
<h3 className="font-medium">已生成区间 (预览):</h3>
|
||||
<div className="font-mono text-sm bg-gray-100 p-2 rounded break-all max-h-64 overflow-y-auto">
|
||||
{currentCase.map((interval, idx) => (
|
||||
<div key={idx} className="mb-1">
|
||||
[{interval[0]}, {interval[1]}]
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">总区间数量: {currentCase.length}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-xl font-semibold mb-4">2. 求解与验证</h2>
|
||||
<button
|
||||
onClick={handleSolve}
|
||||
disabled={!currentCase || loading}
|
||||
className="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700 disabled:bg-gray-400 mb-4"
|
||||
>
|
||||
运行选中算法
|
||||
</button>
|
||||
|
||||
{solveResults && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">算法</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">选中数量</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">耗时 (秒)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{solveResults.map((res, idx) => (
|
||||
<tr key={idx}>
|
||||
<td className="px-4 py-2 text-sm font-medium text-gray-900">{res.algorithm}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{res.result_count}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{res.time_seconds?.toFixed(6)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section 2: Benchmark */}
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-xl font-semibold">3. 性能对比测试</h2>
|
||||
<button
|
||||
onClick={handleBenchmark}
|
||||
disabled={loading}
|
||||
className="bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 disabled:bg-gray-400"
|
||||
>
|
||||
运行性能测试
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{benchmarkResults && (
|
||||
<div className="h-96 w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart
|
||||
data={benchmarkResults}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="size" label={{ value: '区间数量 (N)', position: 'insideBottomRight', offset: -10 }} />
|
||||
<YAxis label={{ value: '耗时 (秒)', angle: -90, position: 'insideLeft' }} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="greedy" stroke="#00ff00" name="贪心算法" />
|
||||
<Line type="monotone" dataKey="brute_force" stroke="#ff0000" name="暴力求解" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IntervalScheduling;
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
import React, { useState } from 'react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
import { generateKnapsackCase, solveKnapsackCase, runKnapsackBenchmark } from '../api';
|
||||
|
||||
const Knapsack = () => {
|
||||
const [params, setParams] = useState({ num_items: 10, max_weight: 50, max_value: 100 });
|
||||
const [currentCase, setCurrentCase] = useState(null);
|
||||
const [solveResults, setSolveResults] = useState(null);
|
||||
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Algorithm selection state
|
||||
const [selectedAlgorithms, setSelectedAlgorithms] = useState({
|
||||
dp: true,
|
||||
backtracking: true
|
||||
});
|
||||
|
||||
const algorithmsList = [
|
||||
{ id: 'dp', name: '动态规划 (DP) O(NW)' },
|
||||
{ id: 'backtracking', name: '回溯算法 (Backtracking) O(2^N)' }
|
||||
];
|
||||
|
||||
const getSelectedAlgoKeys = () => {
|
||||
return Object.keys(selectedAlgorithms).filter(k => selectedAlgorithms[k]);
|
||||
};
|
||||
|
||||
const handleGenerate = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await generateKnapsackCase(params.num_items, params.max_weight, params.max_value);
|
||||
setCurrentCase(data);
|
||||
setSolveResults(null);
|
||||
} catch (error) {
|
||||
console.error("Error generating case:", error);
|
||||
alert("生成测试用例失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleSolve = async () => {
|
||||
if (!currentCase) return;
|
||||
const algos = getSelectedAlgoKeys();
|
||||
if (algos.length === 0) {
|
||||
alert("请至少选择一种算法");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const results = await solveKnapsackCase(currentCase.items, currentCase.capacity, algos);
|
||||
setSolveResults(results);
|
||||
} catch (error) {
|
||||
console.error("Error solving case:", error);
|
||||
alert("求解失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleBenchmark = async () => {
|
||||
const algos = getSelectedAlgoKeys();
|
||||
if (algos.length === 0) {
|
||||
alert("请至少选择一种算法");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const sizes = [10, 15, 20, 22, 25];
|
||||
const results = await runKnapsackBenchmark(sizes, algos);
|
||||
|
||||
const chartData = results.map(item => {
|
||||
const point = { size: item.size };
|
||||
item.algorithms.forEach(algo => {
|
||||
if (algo.time_seconds !== undefined && algo.time_seconds !== null) {
|
||||
point[algo.algorithm] = algo.time_seconds;
|
||||
}
|
||||
});
|
||||
return point;
|
||||
});
|
||||
|
||||
setBenchmarkResults(chartData);
|
||||
} catch (error) {
|
||||
console.error("Error running benchmark:", error);
|
||||
alert("性能测试失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const toggleAlgorithm = (algoId) => {
|
||||
setSelectedAlgorithms(prev => ({
|
||||
...prev,
|
||||
[algoId]: !prev[algoId]
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-8">
|
||||
<h1 className="text-3xl font-bold text-gray-800">0/1 背包问题 (0/1 Knapsack)</h1>
|
||||
<p className="text-gray-600">目标:在不超过背包容量的情况下,选择物品使得总价值最大。</p>
|
||||
|
||||
{/* Algorithm Selection */}
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-xl font-semibold mb-4">算法选择</h2>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{algorithmsList.map(algo => (
|
||||
<label key={algo.id} className="flex items-center space-x-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedAlgorithms[algo.id]}
|
||||
onChange={() => toggleAlgorithm(algo.id)}
|
||||
className="form-checkbox h-5 w-5 text-blue-600"
|
||||
/>
|
||||
<span className="text-gray-700">{algo.name}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section 1: Generate & Solve */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-xl font-semibold mb-4">1. 生成测试用例</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">物品数量</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.num_items}
|
||||
onChange={(e) => setParams({ ...params, num_items: parseInt(e.target.value) })}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">最大重量</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.max_weight}
|
||||
onChange={(e) => setParams({ ...params, max_weight: parseInt(e.target.value) })}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">最大价值</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.max_value}
|
||||
onChange={(e) => setParams({ ...params, max_value: parseInt(e.target.value) })}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleGenerate}
|
||||
disabled={loading}
|
||||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400"
|
||||
>
|
||||
{loading ? '处理中...' : '生成测试用例'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{currentCase && (
|
||||
<div className="mt-4">
|
||||
<h3 className="font-medium">背包容量: {currentCase.capacity}</h3>
|
||||
<h3 className="font-medium mt-2">物品列表 (预览):</h3>
|
||||
<div className="font-mono text-sm bg-gray-100 p-2 rounded break-all max-h-64 overflow-y-auto">
|
||||
<table className="min-w-full text-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">ID</th>
|
||||
<th className="text-left">重量</th>
|
||||
<th className="text-left">价值</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{currentCase.items.map((item) => (
|
||||
<tr key={item.id}>
|
||||
<td>{item.id}</td>
|
||||
<td>{item.weight}</td>
|
||||
<td>{item.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-xl font-semibold mb-4">2. 求解与验证</h2>
|
||||
<button
|
||||
onClick={handleSolve}
|
||||
disabled={!currentCase || loading}
|
||||
className="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700 disabled:bg-gray-400 mb-4"
|
||||
>
|
||||
运行选中算法
|
||||
</button>
|
||||
|
||||
{solveResults && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">算法</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">最大价值</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">耗时 (秒)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{solveResults.map((res, idx) => (
|
||||
<tr key={idx}>
|
||||
<td className="px-4 py-2 text-sm font-medium text-gray-900">{res.algorithm}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{res.max_value}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{res.time_seconds?.toFixed(6)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section 2: Benchmark */}
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-xl font-semibold">3. 性能对比测试</h2>
|
||||
<button
|
||||
onClick={handleBenchmark}
|
||||
disabled={loading}
|
||||
className="bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 disabled:bg-gray-400"
|
||||
>
|
||||
运行性能测试
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{benchmarkResults && (
|
||||
<div className="h-96 w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart
|
||||
data={benchmarkResults}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="size" label={{ value: '物品数量 (N)', position: 'insideBottomRight', offset: -10 }} />
|
||||
<YAxis label={{ value: '耗时 (秒)', angle: -90, position: 'insideLeft' }} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="dp" stroke="#00ff00" name="动态规划" />
|
||||
<Line type="monotone" dataKey="backtracking" stroke="#ff0000" name="回溯算法" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Knapsack;
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
import React, { useState } from 'react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
import { generateCase, solveCase, runBenchmark } from '../api';
|
||||
|
||||
const MaxSubarray = () => {
|
||||
const [params, setParams] = useState({ size: 100, min: -100, max: 100 });
|
||||
const [currentCase, setCurrentCase] = useState(null);
|
||||
const [solveResults, setSolveResults] = useState(null);
|
||||
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Algorithm selection state
|
||||
const [selectedAlgorithms, setSelectedAlgorithms] = useState({
|
||||
brute_force: true,
|
||||
divide_conquer: true,
|
||||
kadane: true
|
||||
});
|
||||
|
||||
const algorithmsList = [
|
||||
{ id: 'brute_force', name: '暴力求解 (Brute Force) O(N^2)' },
|
||||
{ id: 'divide_conquer', name: '分治算法 (Divide & Conquer) O(N log N)' },
|
||||
{ id: 'kadane', name: 'Kadane算法 (Kadane) O(N)' }
|
||||
];
|
||||
|
||||
const getSelectedAlgoKeys = () => {
|
||||
return Object.keys(selectedAlgorithms).filter(k => selectedAlgorithms[k]);
|
||||
};
|
||||
|
||||
const handleGenerate = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await generateCase(params.size, params.min, params.max);
|
||||
setCurrentCase(data.array);
|
||||
setSolveResults(null);
|
||||
} catch (error) {
|
||||
console.error("Error generating case:", error);
|
||||
alert("生成测试用例失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleSolve = async () => {
|
||||
if (!currentCase) return;
|
||||
const algos = getSelectedAlgoKeys();
|
||||
if (algos.length === 0) {
|
||||
alert("请至少选择一种算法");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const results = await solveCase(currentCase, algos);
|
||||
setSolveResults(results);
|
||||
} catch (error) {
|
||||
console.error("Error solving case:", error);
|
||||
alert("求解失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleBenchmark = async () => {
|
||||
const algos = getSelectedAlgoKeys();
|
||||
if (algos.length === 0) {
|
||||
alert("请至少选择一种算法");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// Smaller sizes for quick demo
|
||||
const sizes = [10, 50, 100, 200, 500, 1000, 2000];
|
||||
const results = await runBenchmark(sizes, algos);
|
||||
|
||||
// Transform data for Recharts
|
||||
const chartData = results.map(item => {
|
||||
const point = { size: item.size };
|
||||
item.algorithms.forEach(algo => {
|
||||
if (algo.time_seconds !== undefined && algo.time_seconds !== null) {
|
||||
point[algo.algorithm] = algo.time_seconds;
|
||||
}
|
||||
});
|
||||
return point;
|
||||
});
|
||||
|
||||
setBenchmarkResults(chartData);
|
||||
} catch (error) {
|
||||
console.error("Error running benchmark:", error);
|
||||
alert("性能测试失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const toggleAlgorithm = (algoId) => {
|
||||
setSelectedAlgorithms(prev => ({
|
||||
...prev,
|
||||
[algoId]: !prev[algoId]
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-8">
|
||||
<h1 className="text-3xl font-bold text-gray-800">最大子数组和问题 (Maximum Subarray Sum)</h1>
|
||||
|
||||
{/* Algorithm Selection */}
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-xl font-semibold mb-4">算法选择</h2>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{algorithmsList.map(algo => (
|
||||
<label key={algo.id} className="flex items-center space-x-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedAlgorithms[algo.id]}
|
||||
onChange={() => toggleAlgorithm(algo.id)}
|
||||
className="form-checkbox h-5 w-5 text-blue-600"
|
||||
/>
|
||||
<span className="text-gray-700">{algo.name}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section 1: Generate & Solve */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-xl font-semibold mb-4">1. 生成测试用例</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">数据规模 (N)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.size}
|
||||
onChange={(e) => setParams({ ...params, size: parseInt(e.target.value) })}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">最小值</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.min}
|
||||
onChange={(e) => setParams({ ...params, min: parseInt(e.target.value) })}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">最大值</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.max}
|
||||
onChange={(e) => setParams({ ...params, max: parseInt(e.target.value) })}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleGenerate}
|
||||
disabled={loading}
|
||||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400"
|
||||
>
|
||||
{loading ? '处理中...' : '生成测试用例'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{currentCase && (
|
||||
<div className="mt-4">
|
||||
<h3 className="font-medium">已生成数组 (预览):</h3>
|
||||
<p className="font-mono text-sm bg-gray-100 p-2 rounded break-all max-h-32 overflow-y-auto">
|
||||
[{currentCase.slice(0, 50).join(', ')}{currentCase.length > 50 ? '...' : ''}]
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">总元素数量: {currentCase.length}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h2 className="text-xl font-semibold mb-4">2. 求解与验证</h2>
|
||||
<button
|
||||
onClick={handleSolve}
|
||||
disabled={!currentCase || loading}
|
||||
className="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700 disabled:bg-gray-400 mb-4"
|
||||
>
|
||||
运行选中算法
|
||||
</button>
|
||||
|
||||
{solveResults && (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">算法</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">结果</th>
|
||||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">耗时 (秒)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{solveResults.map((res, idx) => (
|
||||
<tr key={idx}>
|
||||
<td className="px-4 py-2 text-sm font-medium text-gray-900">{res.algorithm}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{res.result}</td>
|
||||
<td className="px-4 py-2 text-sm text-gray-500">{res.time_seconds?.toFixed(6)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section 2: Benchmark */}
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-xl font-semibold">3. 性能对比测试</h2>
|
||||
<button
|
||||
onClick={handleBenchmark}
|
||||
disabled={loading}
|
||||
className="bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 disabled:bg-gray-400"
|
||||
>
|
||||
运行性能测试
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{benchmarkResults && (
|
||||
<div className="h-96 w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart
|
||||
data={benchmarkResults}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="size" label={{ value: '数据规模 (N)', position: 'insideBottomRight', offset: -10 }} />
|
||||
<YAxis label={{ value: '耗时 (秒)', angle: -90, position: 'insideLeft' }} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="brute_force" stroke="#ff0000" name="暴力求解" />
|
||||
<Line type="monotone" dataKey="divide_conquer" stroke="#00ff00" name="分治算法" />
|
||||
<Line type="monotone" dataKey="kadane" stroke="#0000ff" name="Kadane算法" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MaxSubarray;
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
import React, { useState } from 'react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
import { generateNQueensCase, solveNQueensCase, runNQueensBenchmark } from '../api';
|
||||
|
||||
const NQueens = () => {
|
||||
const [n, setN] = useState(8);
|
||||
const [currentSolution, setCurrentSolution] = useState(null);
|
||||
const [solutionCount, setSolutionCount] = useState(null);
|
||||
const [solveTime, setSolveTime] = useState(null);
|
||||
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSolve = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// Generate is trivial (just N), so we skip explicit generate step for UI simplicity
|
||||
// and just call solve directly
|
||||
const results = await solveNQueensCase(n, ["backtracking"]);
|
||||
if (results && results.length > 0) {
|
||||
const res = results[0];
|
||||
setCurrentSolution(res.first_solution);
|
||||
setSolutionCount(res.solution_count);
|
||||
setSolveTime(res.time_seconds);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error solving N-Queens:", error);
|
||||
alert("求解失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleBenchmark = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const sizes = [4, 8, 10, 12, 13];
|
||||
const results = await runNQueensBenchmark(sizes, ["backtracking"]);
|
||||
|
||||
const chartData = results.map(item => {
|
||||
const point = { size: item.size };
|
||||
item.algorithms.forEach(algo => {
|
||||
if (algo.time_seconds !== undefined && algo.time_seconds !== null) {
|
||||
point[algo.algorithm] = algo.time_seconds;
|
||||
}
|
||||
});
|
||||
return point;
|
||||
});
|
||||
|
||||
setBenchmarkResults(chartData);
|
||||
} catch (error) {
|
||||
console.error("Error running benchmark:", error);
|
||||
alert("性能测试失败");
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
// Render the chess board
|
||||
const renderBoard = () => {
|
||||
if (!currentSolution) return null;
|
||||
|
||||
const boardSize = 400;
|
||||
const cellSize = boardSize / n;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative border-2 border-gray-800"
|
||||
style={{ width: boardSize, height: boardSize }}
|
||||
>
|
||||
{Array.from({ length: n }).map((_, row) => (
|
||||
Array.from({ length: n }).map((_, col) => {
|
||||
const isBlack = (row + col) % 2 === 1;
|
||||
const hasQueen = currentSolution[row] === col;
|
||||
return (
|
||||
<div
|
||||
key={`${row}-${col}`}
|
||||
className={`absolute flex items-center justify-center ${isBlack ? 'bg-gray-600' : 'bg-white'}`}
|
||||
style={{
|
||||
width: cellSize,
|
||||
height: cellSize,
|
||||
top: row * cellSize,
|
||||
left: col * cellSize
|
||||
}}
|
||||
>
|
||||
{hasQueen && <span className="text-red-500 font-bold" style={{ fontSize: cellSize * 0.7 }}>♛</span>}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-8">
|
||||
<h1 className="text-3xl font-bold text-gray-800">N皇后问题 (N-Queens)</h1>
|
||||
<p className="text-gray-600">目标:在 N×N 的棋盘上放置 N 个皇后,使得它们互不攻击。</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Controls */}
|
||||
<div className="bg-white p-6 rounded-lg shadow space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">棋盘大小 (N)</label>
|
||||
<input
|
||||
type="number"
|
||||
min="4"
|
||||
max="14"
|
||||
value={n}
|
||||
onChange={(e) => setN(parseInt(e.target.value))}
|
||||
className="mt-1 block w-full border rounded-md p-2"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">建议 N ≤ 14,否则计算时间过长</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleSolve}
|
||||
disabled={loading}
|
||||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400"
|
||||
>
|
||||
{loading ? '计算中...' : '求解 (Backtracking)'}
|
||||
</button>
|
||||
|
||||
{solutionCount !== null && (
|
||||
<div className="mt-4 p-4 bg-green-50 rounded-md">
|
||||
<p className="font-medium text-green-800">找到解的数量: {solutionCount}</p>
|
||||
<p className="text-sm text-green-600">耗时: {solveTime?.toFixed(6)} 秒</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Visualization */}
|
||||
<div className="bg-white p-6 rounded-lg shadow flex justify-center items-center min-h-[450px]">
|
||||
{currentSolution ? renderBoard() : (
|
||||
<div className="text-gray-400 text-center">
|
||||
<p>点击求解以查看结果</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Benchmark */}
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-xl font-semibold">性能测试 (N=4 to 13)</h2>
|
||||
<button
|
||||
onClick={handleBenchmark}
|
||||
disabled={loading}
|
||||
className="bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 disabled:bg-gray-400"
|
||||
>
|
||||
运行性能测试
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{benchmarkResults && (
|
||||
<div className="h-96 w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart
|
||||
data={benchmarkResults}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="size" label={{ value: 'N', position: 'insideBottomRight', offset: -10 }} />
|
||||
<YAxis label={{ value: '耗时 (秒)', angle: -90, position: 'insideLeft' }} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="backtracking" stroke="#ff0000" name="回溯算法" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NQueens;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
upstream backend {
|
||||
server backend:8000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# Frontend static files
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Proxy API requests to backend
|
||||
location /api/ {
|
||||
proxy_pass http://backend/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Proxy docs to backend
|
||||
location /docs {
|
||||
proxy_pass http://backend/docs;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
location /openapi.json {
|
||||
proxy_pass http://backend/openapi.json;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue