stashcom
This commit is contained in:
@@ -37,7 +37,7 @@ def script():
|
|||||||
|
|
||||||
@app.route('/favicon.ico')
|
@app.route('/favicon.ico')
|
||||||
def favicon():
|
def favicon():
|
||||||
return send_from_directory("templates", "favicon", "favicon.ico")
|
return send_from_directory("templates", "favicon.ico")
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
|
|||||||
60
src/problems/Palindrome/description.md
Normal file
60
src/problems/Palindrome/description.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
## Problem: Check if a String is a Palindrome
|
||||||
|
|
||||||
|
Given a string `s`, determine whether it reads the same forward and backward.
|
||||||
|
Return `True` if it is a palindrome, otherwise return `False`.
|
||||||
|
|
||||||
|
A **palindrome** is a sequence of characters that is identical when reversed.
|
||||||
|
Comparison is **case-sensitive** and should consider all characters, including spaces and punctuation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Example 1
|
||||||
|
|
||||||
|
**Input:**
|
||||||
|
|
||||||
|
```
|
||||||
|
s = "racecar"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
|
||||||
|
```
|
||||||
|
True
|
||||||
|
```
|
||||||
|
|
||||||
|
**Explanation:**
|
||||||
|
Reversing `"racecar"` results in `"racecar"`, which is the same as the original string.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Example 2
|
||||||
|
|
||||||
|
**Input:**
|
||||||
|
|
||||||
|
```
|
||||||
|
s = "hello"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
|
||||||
|
```
|
||||||
|
False
|
||||||
|
```
|
||||||
|
|
||||||
|
**Explanation:**
|
||||||
|
Reversing `"hello"` results in `"olleh"`, which is different from the original string.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Constraints
|
||||||
|
|
||||||
|
* `0 <= len(s) <= 10^5`
|
||||||
|
* `s` may contain letters, digits, symbols, and spaces.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Function Signature (Python)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def palindrome(s: str) -> bool:
|
||||||
|
```
|
||||||
7
src/problems/Palindrome/manifest.json
Normal file
7
src/problems/Palindrome/manifest.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"title": "Palindrome",
|
||||||
|
"description": "Find out wether or not a String is a Palindrome",
|
||||||
|
"description_md": "problems/Palindrome/description.md",
|
||||||
|
"test_code": "problems/Palindrome/test.py",
|
||||||
|
"difficulty": "medium"
|
||||||
|
}
|
||||||
32
src/problems/Palindrome/test.py
Normal file
32
src/problems/Palindrome/test.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
#<!-- User expected Function -->
|
||||||
|
## def palindrome(s:str) -> bool:
|
||||||
|
## return s == s[::-1]
|
||||||
|
|
||||||
|
class TestSolution(unittest.TestCase):
|
||||||
|
def test_palindrome(self):
|
||||||
|
test_cases = [
|
||||||
|
("racecar", True), # Simple palindrome
|
||||||
|
("hello", False), # Not a palindrome
|
||||||
|
("", True), # Empty string
|
||||||
|
("a", True), # Single character
|
||||||
|
("madam", True), # Palindrome word
|
||||||
|
("Madam", False), # Case-sensitive check
|
||||||
|
("12321", True), # Numeric string palindrome
|
||||||
|
("123456", False), # Numeric string non-palindrome
|
||||||
|
]
|
||||||
|
print("\nFUNCTION OUTPUT TEST RESULTS")
|
||||||
|
|
||||||
|
for input_val, expected in test_cases:
|
||||||
|
try:
|
||||||
|
actual = palindrome(input_val) # pyright: ignore[reportUndefinedVariable]
|
||||||
|
status = "✓ PASS" if actual == expected else "✗ FAIL"
|
||||||
|
print(f"{status} | Input: '{input_val}' -> Got: {actual} | Expected: {expected}")
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ ERROR | Input: '{input_val}' -> Exception: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main(verbosity=2)
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -28,6 +28,7 @@ header {
|
|||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
header h1 {
|
header h1 {
|
||||||
|
text-align: center;
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
}
|
}
|
||||||
@@ -183,3 +184,24 @@ header p {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Leaderboard horizontal collapse */
|
||||||
|
#leaderboardSection {
|
||||||
|
transition: max-width 0.35s ease, opacity 0.25s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leaderboardSection.hidden {
|
||||||
|
max-width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leaderboardSection.visible {
|
||||||
|
max-width: 100%; /* take full available space in grid column */
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
#rankingExplanation {
|
||||||
|
transition: all 0.35s ease;
|
||||||
|
}
|
||||||
|
|||||||
212
src/static/problem.css
Normal file
212
src/static/problem.css
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #333;
|
||||||
|
min-height: 100vh; /* allow content to grow */
|
||||||
|
overflow-y: auto; /* allow vertical scroll */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap; /* wrap on small screens */
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-panel {
|
||||||
|
flex: 1 1 400px; /* grow/shrink with base 400px */
|
||||||
|
min-width: 300px;
|
||||||
|
background: white;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
border-right: 1px solid #eaeaea;
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-container {
|
||||||
|
flex: 1 1 400px;
|
||||||
|
min-width: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: white;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow: hidden; /* internal scroll handling */
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header {
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-bottom: 1px solid #eaeaea;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-wrapper {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
margin-right: 15px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-desc {
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 15px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-desc pre {
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-desc code {
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-actions {
|
||||||
|
padding: 15px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-actions button {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-actions button:hover {
|
||||||
|
background-color: #0069d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 300px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-panel {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
min-height: 120px;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 30vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-panel h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-panel pre {
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.main-container {
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
}
|
||||||
|
.problem-panel, .editor-container {
|
||||||
|
flex: none;
|
||||||
|
width: 100%;
|
||||||
|
min-width: auto;
|
||||||
|
max-height: none;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid #eaeaea;
|
||||||
|
}
|
||||||
|
#editor {
|
||||||
|
min-height: 400px;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
.result-panel {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,17 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Quick Problem Platform</title>
|
<title>Quick Problem Platform</title>
|
||||||
<!--<link rel="favicon" href="/favicon/favicon.ico">-->
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
<script src="script.js" async defer></script>
|
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
|
||||||
<link rel="stylesheet" href="/static/index.css">
|
<style>
|
||||||
|
/* Minimal inline CSS to support full-width explanation and popout */
|
||||||
|
#rankingExplanation {
|
||||||
|
display:none;
|
||||||
|
grid-column: 1 / span 2;
|
||||||
|
transition: all 0.35s ease;
|
||||||
|
}
|
||||||
|
#rankInfoBtn.active { color: #2563eb; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
@@ -16,47 +23,30 @@
|
|||||||
</header>
|
</header>
|
||||||
<div class="content" id="contentContainer">
|
<div class="content" id="contentContainer">
|
||||||
<!-- Problems -->
|
<!-- Problems -->
|
||||||
<section class="card problems-list">
|
<section class="card problems-list">
|
||||||
<div class="search-controls">
|
<div class="search-controls">
|
||||||
<input
|
<input type="text" class="search-input" id="problemSearch" placeholder="Search problems..." />
|
||||||
type="text"
|
|
||||||
class="search-input"
|
|
||||||
id="problemSearch"
|
|
||||||
placeholder="Search problems..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h2 style="margin-bottom:6px;font-size:1.1rem">Problems</h2>
|
|
||||||
<div id="problemsContainer">
|
|
||||||
{% for folder, description, test_code, difficulty in problems %}
|
|
||||||
<div class="problem-item" data-name="{{ folder.replace('_',' ').title() }}" data-desc="{{ description }}">
|
|
||||||
<a href="/problem/{{ folder }}">{{ folder.replace('_',' ').title() }}</a>
|
|
||||||
<span class="difficulty" data-difficulty="{{ difficulty|lower }}">{{ difficulty }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
<h2 style="margin-bottom:6px;font-size:1.1rem">Problems</h2>
|
||||||
<div class="problem-item">No problems yet.</div>
|
<div id="problemsContainer">
|
||||||
{% endfor %}
|
{% for folder, description, test_code, difficulty in problems %}
|
||||||
</div>
|
<div class="problem-item" data-name="{{ folder.replace('_',' ').title() }}" data-desc="{{ description }}">
|
||||||
</section>
|
<a href="/problem/{{ folder }}">{{ folder.replace('_',' ').title() }}</a>
|
||||||
|
<span class="difficulty" data-difficulty="{{ difficulty|lower }}">{{ difficulty }}</span>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="problem-item">No problems yet.</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Leaderboard -->
|
<!-- Leaderboard -->
|
||||||
<section class="card" id="leaderboardSection">
|
<section class="card" id="leaderboardSection">
|
||||||
<div class="leaderboard-head">
|
<div class="leaderboard-head">
|
||||||
<h2 style="font-size:1.1rem;margin:0">Leaderboard</h2>
|
<h2 style="font-size:1.1rem;margin:0">Leaderboard</h2>
|
||||||
<button class="btn" id="toggleLeaderboard">Hide</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="leaderboard-controls">
|
<div class="leaderboard-controls">
|
||||||
<input
|
<input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." />
|
||||||
type="text"
|
|
||||||
class="search-input"
|
|
||||||
id="userSearch"
|
|
||||||
placeholder="Filter by user..."
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="search-input"
|
|
||||||
id="problemFilter"
|
|
||||||
placeholder="Filter by problem..."
|
|
||||||
/>
|
|
||||||
<select class="filter-select" id="runtimeFilter">
|
<select class="filter-select" id="runtimeFilter">
|
||||||
<option value="">All runtimes</option>
|
<option value="">All runtimes</option>
|
||||||
<option value="best">Best runtime</option>
|
<option value="best">Best runtime</option>
|
||||||
@@ -67,7 +57,7 @@
|
|||||||
<table class="leaderboard-table">
|
<table class="leaderboard-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="sortable" data-sort="rank">Rank</th>
|
<th class="sortable" data-sort="rank">Rank <span id="rankInfoBtn" title="How ranking works" style="cursor:pointer;">ℹ️</span></th>
|
||||||
<th class="sortable" data-sort="user">User</th>
|
<th class="sortable" data-sort="user">User</th>
|
||||||
<th class="sortable" data-sort="problem">Problem</th>
|
<th class="sortable" data-sort="problem">Problem</th>
|
||||||
<th class="sortable" data-sort="runtime">Runtime (s)</th>
|
<th class="sortable" data-sort="runtime">Runtime (s)</th>
|
||||||
@@ -83,7 +73,14 @@
|
|||||||
data-timestamp="{{ entry[5] }}">
|
data-timestamp="{{ entry[5] }}">
|
||||||
<td>{{ loop.index }}</td>
|
<td>{{ loop.index }}</td>
|
||||||
<td>{{ entry[0] }}</td>
|
<td>{{ entry[0] }}</td>
|
||||||
<td>{{ problem_titles.get(entry[1], 'Unknown') }}</td>
|
<td>
|
||||||
|
<a href="/problem/{{ problem_titles.get(entry[1], 'Unknown') }}"
|
||||||
|
style="color: #0077ff; text-decoration: none;"
|
||||||
|
onmouseover="this.style.textDecoration='underline';"
|
||||||
|
onmouseout="this.style.textDecoration='none';">
|
||||||
|
{{ problem_titles.get(entry[1], 'Unknown') }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td>{{ '%.4f'|format(entry[2]) }}</td>
|
<td>{{ '%.4f'|format(entry[2]) }}</td>
|
||||||
<td>{{ entry[3] }}</td>
|
<td>{{ entry[3] }}</td>
|
||||||
<td>{{ entry[4] if entry[4] else '-' }}</td>
|
<td>{{ entry[4] if entry[4] else '-' }}</td>
|
||||||
@@ -96,7 +93,150 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Ranking explanation popout -->
|
||||||
|
<section class="card" id="rankingExplanation">
|
||||||
|
<h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2>
|
||||||
|
<p>
|
||||||
|
The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank.
|
||||||
|
It considers multiple metrics for each submission:
|
||||||
|
</p>
|
||||||
|
<ul style="margin-left: 15px;">
|
||||||
|
<li><strong>Runtime:</strong> How fast the solution runs (lower is better).</li>
|
||||||
|
<li><strong>Memory Usage:</strong> How much memory the solution uses (lower is better).</li>
|
||||||
|
</ul>
|
||||||
|
<p>Each metric is normalized against the best in the leaderboard, and the overall score is calculated as:</p>
|
||||||
|
<div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;">
|
||||||
|
<code>
|
||||||
|
runtimeScore = yourRuntime / bestRuntime<br>
|
||||||
|
memoryScore = yourMemory / bestMemory<br>
|
||||||
|
overallScore = runtimeScore × 0.7 + memoryScore × 0.3
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
<p>Lower overall scores are better. If two scores are equal, the earlier submission ranks higher.</p>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Inline JS -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Problem search
|
||||||
|
const problemSearch = document.getElementById('problemSearch');
|
||||||
|
const problemsContainer = document.getElementById('problemsContainer');
|
||||||
|
const problemItems = problemsContainer.querySelectorAll('.problem-item');
|
||||||
|
|
||||||
|
problemSearch?.addEventListener('input', () => {
|
||||||
|
const term = problemSearch.value.toLowerCase();
|
||||||
|
problemItems.forEach(item => {
|
||||||
|
const name = item.dataset.name.toLowerCase();
|
||||||
|
const desc = item.dataset.desc?.toLowerCase() || '';
|
||||||
|
item.style.display = (name.includes(term) || desc.includes(term)) ? '' : 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Leaderboard
|
||||||
|
const problemFilter = document.getElementById('problemFilter');
|
||||||
|
const runtimeFilter = document.getElementById('runtimeFilter');
|
||||||
|
const leaderboardBody = document.getElementById('leaderboardBody');
|
||||||
|
const leaderboardRows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
||||||
|
const sortableHeaders = document.querySelectorAll('.sortable');
|
||||||
|
|
||||||
|
let currentSort = { column: null, direction: 'asc' };
|
||||||
|
|
||||||
|
function filterLeaderboard() {
|
||||||
|
const problemTerm = problemFilter.value.toLowerCase();
|
||||||
|
const runtimeType = runtimeFilter.value;
|
||||||
|
|
||||||
|
leaderboardRows.forEach(row => {
|
||||||
|
const problem = row.dataset.problem.toLowerCase();
|
||||||
|
const runtime = parseFloat(row.dataset.runtime);
|
||||||
|
const showProblem = problem.includes(problemTerm);
|
||||||
|
|
||||||
|
let showRuntime = true;
|
||||||
|
if(runtimeType === 'best') {
|
||||||
|
const userProblemRows = leaderboardRows.filter(r => r.dataset.user === row.dataset.user && r.dataset.problem === row.dataset.problem);
|
||||||
|
const best = Math.min(...userProblemRows.map(r=>parseFloat(r.dataset.runtime)));
|
||||||
|
showRuntime = runtime === best;
|
||||||
|
} else if(runtimeType === 'worst') {
|
||||||
|
const userProblemRows = leaderboardRows.filter(r => r.dataset.user === row.dataset.user && r.dataset.problem === row.dataset.problem);
|
||||||
|
const worst = Math.max(...userProblemRows.map(r=>parseFloat(r.dataset.runtime)));
|
||||||
|
showRuntime = runtime === worst;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.style.display = (showProblem && showRuntime) ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
problemFilter?.addEventListener('input', filterLeaderboard);
|
||||||
|
runtimeFilter?.addEventListener('change', filterLeaderboard);
|
||||||
|
|
||||||
|
function getCellValue(row, column) {
|
||||||
|
const index = Array.from(document.querySelectorAll('th')).findIndex(th => th.dataset.sort===column);
|
||||||
|
let val = row.cells[index].textContent.trim();
|
||||||
|
if(['runtime','memory','rank'].includes(column)) return parseFloat(val)||0;
|
||||||
|
if(column==='timestamp') return new Date(val).getTime();
|
||||||
|
return val.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareValues(a,b,direction) {
|
||||||
|
if(typeof a==='number' && typeof b==='number') return direction==='asc'?a-b:b-a;
|
||||||
|
if(a<b) return direction==='asc'?-1:1;
|
||||||
|
if(a>b) return direction==='asc'?1:-1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateOverallRank() {
|
||||||
|
const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
||||||
|
const runtimes = rows.map(r=>parseFloat(r.dataset.runtime)||Infinity);
|
||||||
|
const memories = rows.map(r=>parseFloat(r.dataset.memory)||Infinity);
|
||||||
|
const minRuntime = Math.min(...runtimes);
|
||||||
|
const minMemory = Math.min(...memories);
|
||||||
|
|
||||||
|
rows.forEach(row=>{
|
||||||
|
const runtimeScore = (parseFloat(row.dataset.runtime)||Infinity)/minRuntime;
|
||||||
|
const memoryScore = (parseFloat(row.dataset.memory)||Infinity)/minMemory;
|
||||||
|
const score = runtimeScore*0.7 + memoryScore*0.3;
|
||||||
|
row.dataset.overallScore = score;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortLeaderboard(column,direction) {
|
||||||
|
let rows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
||||||
|
if(column==='rank') {
|
||||||
|
calculateOverallRank();
|
||||||
|
rows.sort((a,b)=>parseFloat(a.dataset.overallScore)-parseFloat(b.dataset.overallScore));
|
||||||
|
rows.forEach((row,index)=>row.cells[0].textContent = index+1);
|
||||||
|
} else {
|
||||||
|
rows.sort((a,b)=>compareValues(getCellValue(a,column),getCellValue(b,column),direction));
|
||||||
|
}
|
||||||
|
rows.forEach(row=>leaderboardBody.appendChild(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
sortableHeaders.forEach(header=>{
|
||||||
|
header.addEventListener('click',()=>{
|
||||||
|
const column = header.dataset.sort;
|
||||||
|
sortableHeaders.forEach(h=>h.classList.remove('sort-asc','sort-desc'));
|
||||||
|
if(currentSort.column===column) currentSort.direction = currentSort.direction==='asc'?'desc':'asc';
|
||||||
|
else { currentSort.column=column; currentSort.direction='asc'; }
|
||||||
|
header.classList.add(`sort-${currentSort.direction}`);
|
||||||
|
sortLeaderboard(column,currentSort.direction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rank info popout
|
||||||
|
const rankInfoBtn = document.getElementById('rankInfoBtn');
|
||||||
|
const rankingExplanation = document.getElementById('rankingExplanation');
|
||||||
|
rankInfoBtn?.addEventListener('click',()=>{
|
||||||
|
if(rankingExplanation.style.display==='none' || rankingExplanation.style.display==='') {
|
||||||
|
rankingExplanation.style.display='block';
|
||||||
|
rankInfoBtn.classList.add('active');
|
||||||
|
} else {
|
||||||
|
rankingExplanation.style.display='none';
|
||||||
|
rankInfoBtn.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,222 +5,11 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{ problem.title }} - Coding Problem</title>
|
<title>{{ problem.title }} - Coding Problem</title>
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
<link rel="stylesheet" href="/static/problem.css">
|
||||||
|
<!-- this is stoopid fucking html link for favicon. just cause of flask-->
|
||||||
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
<link href="https://fonts.cdnfonts.com/css/jetbrains-mono" rel="stylesheet">
|
<link href="https://fonts.cdnfonts.com/css/jetbrains-mono" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
color: #333;
|
|
||||||
min-height: 100vh; /* allow content to grow */
|
|
||||||
overflow-y: auto; /* allow vertical scroll */
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
*, *::before, *::after {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap; /* wrap on small screens */
|
|
||||||
min-height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.problem-panel {
|
|
||||||
flex: 1 1 400px; /* grow/shrink with base 400px */
|
|
||||||
min-width: 300px;
|
|
||||||
background: white;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px;
|
|
||||||
border-right: 1px solid #eaeaea;
|
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-container {
|
|
||||||
flex: 1 1 400px;
|
|
||||||
min-width: 300px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: white;
|
|
||||||
max-height: 100vh;
|
|
||||||
overflow: hidden; /* internal scroll handling */
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-header {
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-bottom: 1px solid #eaeaea;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-wrapper {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 0;
|
|
||||||
padding: 0 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.problem-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #666;
|
|
||||||
margin-right: 15px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-btn:hover {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
color: #1a1a1a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.problem-desc {
|
|
||||||
line-height: 1.6;
|
|
||||||
font-size: 15px;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.problem-desc pre {
|
|
||||||
background: #f6f8fa;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.problem-desc code {
|
|
||||||
background: #f6f8fa;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-actions {
|
|
||||||
padding: 15px 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-actions button {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-actions button:hover {
|
|
||||||
background-color: #0069d9;
|
|
||||||
}
|
|
||||||
|
|
||||||
#editor {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-height: 300px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: auto;
|
|
||||||
max-height: 60vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-panel {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
min-height: 120px;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 30vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-panel h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-panel pre {
|
|
||||||
background: #f6f8fa;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow-x: auto;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
color: #999;
|
|
||||||
font-style: italic;
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive adjustments */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.main-container {
|
|
||||||
flex-direction: column;
|
|
||||||
height: auto;
|
|
||||||
overflow-y: visible;
|
|
||||||
}
|
|
||||||
.problem-panel, .editor-container {
|
|
||||||
flex: none;
|
|
||||||
width: 100%;
|
|
||||||
min-width: auto;
|
|
||||||
max-height: none;
|
|
||||||
border-right: none;
|
|
||||||
border-bottom: 1px solid #eaeaea;
|
|
||||||
}
|
|
||||||
#editor {
|
|
||||||
min-height: 400px;
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
.result-panel {
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
|
|||||||
101
src/templates/ranking.html
Normal file
101
src/templates/ranking.html
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Leaderboard Ranking System</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
h1, h2 {
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
.example {
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-left: 4px solid #007acc;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Leaderboard Ranking System</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The leaderboard uses a <strong>weighted scoring system</strong> to determine who ranks first.
|
||||||
|
Rather than simply sorting by a single column, the system considers multiple performance metrics:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Metrics Used</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Runtime</strong> — How fast the solution runs (lower is better).</li>
|
||||||
|
<li><strong>Memory Usage</strong> — How much memory the solution uses (lower is better).</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Score Calculation</h2>
|
||||||
|
<p>
|
||||||
|
Each metric is normalized against the best result in the leaderboard.
|
||||||
|
The formula for the score is:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="example">
|
||||||
|
<code>
|
||||||
|
runtimeScore = (yourRuntime / bestRuntime)<br>
|
||||||
|
memoryScore = (yourMemory / bestMemory)<br>
|
||||||
|
overallScore = runtimeScore × 0.7 + memoryScore × 0.3
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Lower scores are better.</li>
|
||||||
|
<li><code>0.7</code> means runtime is worth 70% of your score.</li>
|
||||||
|
<li><code>0.3</code> means memory usage is worth 30% of your score.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Ranking Rules</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Players are sorted by <strong>overallScore</strong> (lowest first).</li>
|
||||||
|
<li>If two players have the same score, ties are broken by <strong>earlier timestamp</strong> (who submitted first).</li>
|
||||||
|
<li>Ranks are updated dynamically when you click the "Rank" column header.</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Example</h2>
|
||||||
|
<div class="example">
|
||||||
|
<b>Best Runtime:</b> 1.000s
|
||||||
|
<b>Best Memory:</b> 50MB
|
||||||
|
|
||||||
|
Player A: Runtime = 1.200s, Memory = 60MB
|
||||||
|
runtimeScore = 1.200 / 1.000 = 1.20
|
||||||
|
memoryScore = 60 / 50 = 1.20
|
||||||
|
overallScore = (1.20 × 0.7) + (1.20 × 0.3) = 1.20
|
||||||
|
|
||||||
|
Player B: Runtime = 1.100s, Memory = 70MB
|
||||||
|
runtimeScore = 1.100 / 1.000 = 1.10
|
||||||
|
memoryScore = 70 / 50 = 1.40
|
||||||
|
overallScore = (1.10 × 0.7) + (1.40 × 0.3) = 1.19
|
||||||
|
|
||||||
|
<b>Winner:</b> Player B (score 1.19 is better than 1.20)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Why This Method?</h2>
|
||||||
|
<p>
|
||||||
|
This ranking system prevents a player with an extremely low runtime but huge memory usage (or vice versa) from automatically winning.
|
||||||
|
It rewards balanced solutions while still prioritizing speed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,39 +1,22 @@
|
|||||||
// Toggle leaderboard visibility
|
// =======================
|
||||||
const toggleBtn = document.getElementById('toggleLeaderboard');
|
// Problem search
|
||||||
const leaderboardSection = document.getElementById('leaderboardSection');
|
// =======================
|
||||||
const contentContainer = document.getElementById('contentContainer');
|
|
||||||
|
|
||||||
toggleBtn.addEventListener('click', () => {
|
|
||||||
if (leaderboardSection.style.display === 'none') {
|
|
||||||
leaderboardSection.style.display = '';
|
|
||||||
toggleBtn.textContent = 'Hide';
|
|
||||||
contentContainer.classList.remove('single-column');
|
|
||||||
} else {
|
|
||||||
leaderboardSection.style.display = 'none';
|
|
||||||
toggleBtn.textContent = 'Show';
|
|
||||||
contentContainer.classList.add('single-column');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Problem search functionality
|
|
||||||
const problemSearch = document.getElementById('problemSearch');
|
const problemSearch = document.getElementById('problemSearch');
|
||||||
const problemsContainer = document.getElementById('problemsContainer');
|
const problemsContainer = document.getElementById('problemsContainer');
|
||||||
const problemItems = problemsContainer.querySelectorAll('.problem-item');
|
const problemItems = problemsContainer.querySelectorAll('.problem-item');
|
||||||
|
|
||||||
problemSearch.addEventListener('input', () => {
|
problemSearch.addEventListener('input', () => {
|
||||||
const searchTerm = problemSearch.value.toLowerCase();
|
const searchTerm = problemSearch.value.toLowerCase();
|
||||||
problemItems.forEach(item => {
|
problemItems.forEach(item => {
|
||||||
const name = item.dataset.name.toLowerCase();
|
const name = item.dataset.name.toLowerCase();
|
||||||
const desc = item.dataset.desc?.toLowerCase() || '';
|
const desc = item.dataset.desc?.toLowerCase() || '';
|
||||||
if (name.includes(searchTerm) || desc.includes(searchTerm)) {
|
item.style.display = (name.includes(searchTerm) || desc.includes(searchTerm)) ? '' : 'none';
|
||||||
item.style.display = '';
|
});
|
||||||
} else {
|
|
||||||
item.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Leaderboard filtering and sorting
|
// =======================
|
||||||
|
// Leaderboard filtering
|
||||||
|
// =======================
|
||||||
const userSearch = document.getElementById('userSearch');
|
const userSearch = document.getElementById('userSearch');
|
||||||
const problemFilter = document.getElementById('problemFilter');
|
const problemFilter = document.getElementById('problemFilter');
|
||||||
const runtimeFilter = document.getElementById('runtimeFilter');
|
const runtimeFilter = document.getElementById('runtimeFilter');
|
||||||
@@ -41,112 +24,141 @@ const leaderboardBody = document.getElementById('leaderboardBody');
|
|||||||
const leaderboardRows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
const leaderboardRows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
||||||
const sortableHeaders = document.querySelectorAll('.sortable');
|
const sortableHeaders = document.querySelectorAll('.sortable');
|
||||||
|
|
||||||
// Current sort state
|
let currentSort = { column: null, direction: 'asc' };
|
||||||
let currentSort = {
|
|
||||||
column: null,
|
|
||||||
direction: 'asc'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter leaderboard
|
|
||||||
function filterLeaderboard() {
|
function filterLeaderboard() {
|
||||||
const userTerm = userSearch.value.toLowerCase();
|
const userTerm = userSearch.value.toLowerCase();
|
||||||
const problemTerm = problemFilter.value.toLowerCase();
|
const problemTerm = problemFilter.value.toLowerCase();
|
||||||
const runtimeType = runtimeFilter.value;
|
const runtimeType = runtimeFilter.value;
|
||||||
|
|
||||||
leaderboardRows.forEach(row => {
|
leaderboardRows.forEach(row => {
|
||||||
const user = row.dataset.user.toLowerCase();
|
const user = row.dataset.user.toLowerCase();
|
||||||
const problem = row.dataset.problem.toLowerCase();
|
const problem = row.dataset.problem.toLowerCase();
|
||||||
const runtime = parseFloat(row.dataset.runtime);
|
const runtime = parseFloat(row.dataset.runtime);
|
||||||
const showUser = user.includes(userTerm);
|
|
||||||
const showProblem = problem.includes(problemTerm);
|
|
||||||
|
|
||||||
let showRuntime = true;
|
|
||||||
if (runtimeType === 'best') {
|
|
||||||
// Find if this is the best runtime for this user+problem combo
|
|
||||||
const userProblemRows = leaderboardRows.filter(r =>
|
|
||||||
r.dataset.user === row.dataset.user &&
|
|
||||||
r.dataset.problem === row.dataset.problem
|
|
||||||
);
|
|
||||||
const bestRuntime = Math.min(...userProblemRows.map(r => parseFloat(r.dataset.runtime)));
|
|
||||||
showRuntime = runtime === bestRuntime;
|
|
||||||
} else if (runtimeType === 'worst') {
|
|
||||||
// Find if this is the worst runtime for this user+problem combo
|
|
||||||
const userProblemRows = leaderboardRows.filter(r =>
|
|
||||||
r.dataset.user === row.dataset.user &&
|
|
||||||
r.dataset.problem === row.dataset.problem
|
|
||||||
);
|
|
||||||
const worstRuntime = Math.max(...userProblemRows.map(r => parseFloat(r.dataset.runtime)));
|
|
||||||
showRuntime = runtime === worstRuntime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showUser && showProblem && showRuntime) {
|
const showUser = user.includes(userTerm);
|
||||||
row.style.display = '';
|
const showProblem = problem.includes(problemTerm);
|
||||||
} else {
|
|
||||||
row.style.display = 'none';
|
let showRuntime = true;
|
||||||
}
|
if (runtimeType === 'best') {
|
||||||
});
|
const userProblemRows = leaderboardRows.filter(r =>
|
||||||
|
r.dataset.user === row.dataset.user &&
|
||||||
|
r.dataset.problem === row.dataset.problem
|
||||||
|
);
|
||||||
|
const bestRuntime = Math.min(...userProblemRows.map(r => parseFloat(r.dataset.runtime)));
|
||||||
|
showRuntime = runtime === bestRuntime;
|
||||||
|
} else if (runtimeType === 'worst') {
|
||||||
|
const userProblemRows = leaderboardRows.filter(r =>
|
||||||
|
r.dataset.user === row.dataset.user &&
|
||||||
|
r.dataset.problem === row.dataset.problem
|
||||||
|
);
|
||||||
|
const worstRuntime = Math.max(...userProblemRows.map(r => parseFloat(r.dataset.runtime)));
|
||||||
|
showRuntime = runtime === worstRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.style.display = (showUser && showProblem && showRuntime) ? '' : 'none';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort leaderboard
|
// =======================
|
||||||
function sortLeaderboard(column, direction) {
|
// Helper: Get cell value
|
||||||
const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
// =======================
|
||||||
const index = Array.from(document.querySelectorAll('th')).findIndex(th => th.dataset.sort === column);
|
function getCellValue(row, column) {
|
||||||
|
const index = Array.from(document.querySelectorAll('th')).findIndex(th => th.dataset.sort === column);
|
||||||
|
let val = row.cells[index].textContent.trim();
|
||||||
|
|
||||||
rows.sort((a, b) => {
|
if (['runtime', 'memory', 'rank'].includes(column)) return parseFloat(val) || 0;
|
||||||
let aValue = a.cells[index].textContent;
|
if (column === 'timestamp') return new Date(val).getTime();
|
||||||
let bValue = b.cells[index].textContent;
|
return val.toLowerCase();
|
||||||
|
}
|
||||||
// Special handling for numeric columns
|
|
||||||
if (column === 'runtime' || column === 'memory' || column === 'rank') {
|
function compareValues(a, b, direction) {
|
||||||
aValue = parseFloat(aValue) || 0;
|
if (typeof a === 'number' && typeof b === 'number') {
|
||||||
bValue = parseFloat(bValue) || 0;
|
return direction === 'asc' ? a - b : b - a;
|
||||||
return direction === 'asc' ? aValue - bValue : bValue - aValue;
|
|
||||||
}
|
}
|
||||||
|
if (a < b) return direction === 'asc' ? -1 : 1;
|
||||||
// Special handling for timestamps
|
if (a > b) return direction === 'asc' ? 1 : -1;
|
||||||
if (column === 'timestamp') {
|
|
||||||
aValue = new Date(aValue).getTime();
|
|
||||||
bValue = new Date(bValue).getTime();
|
|
||||||
return direction === 'asc' ? aValue - bValue : bValue - aValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default string comparison
|
|
||||||
aValue = aValue.toLowerCase();
|
|
||||||
bValue = bValue.toLowerCase();
|
|
||||||
if (aValue < bValue) return direction === 'asc' ? -1 : 1;
|
|
||||||
if (aValue > bValue) return direction === 'asc' ? 1 : -1;
|
|
||||||
return 0;
|
return 0;
|
||||||
});
|
|
||||||
|
|
||||||
// Re-append rows in sorted order
|
|
||||||
rows.forEach(row => leaderboardBody.appendChild(row));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up event listeners
|
// =======================
|
||||||
|
// Calculate "Overall" score
|
||||||
|
// =======================
|
||||||
|
function calculateOverallRank() {
|
||||||
|
const rows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
||||||
|
const runtimes = rows.map(r => parseFloat(r.dataset.runtime) || Infinity);
|
||||||
|
const memories = rows.map(r => parseFloat(r.dataset.memory) || Infinity);
|
||||||
|
|
||||||
|
const minRuntime = Math.min(...runtimes);
|
||||||
|
const minMemory = Math.min(...memories);
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const runtimeScore = (parseFloat(row.dataset.runtime) || Infinity) / minRuntime;
|
||||||
|
const memoryScore = (parseFloat(row.dataset.memory) || Infinity) / minMemory;
|
||||||
|
// Weighted score: runtime 70%, memory 30%
|
||||||
|
const score = runtimeScore * 0.7 + memoryScore * 0.3;
|
||||||
|
row.dataset.overallScore = score;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================
|
||||||
|
// Sorting
|
||||||
|
// =======================
|
||||||
|
function sortLeaderboard(column, direction) {
|
||||||
|
let rows = Array.from(leaderboardBody.querySelectorAll('tr'));
|
||||||
|
|
||||||
|
if (column === 'rank') {
|
||||||
|
calculateOverallRank();
|
||||||
|
rows.sort((a, b) => parseFloat(a.dataset.overallScore) - parseFloat(b.dataset.overallScore));
|
||||||
|
// Update displayed rank
|
||||||
|
rows.forEach((row, index) => {
|
||||||
|
row.cells[0].textContent = index + 1;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rows.sort((rowA, rowB) => {
|
||||||
|
const valA = getCellValue(rowA, column);
|
||||||
|
const valB = getCellValue(rowB, column);
|
||||||
|
return compareValues(valA, valB, direction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.forEach(row => leaderboardBody.appendChild(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================
|
||||||
|
// Event listeners
|
||||||
|
// =======================
|
||||||
userSearch.addEventListener('input', filterLeaderboard);
|
userSearch.addEventListener('input', filterLeaderboard);
|
||||||
problemFilter.addEventListener('input', filterLeaderboard);
|
problemFilter.addEventListener('input', filterLeaderboard);
|
||||||
runtimeFilter.addEventListener('change', filterLeaderboard);
|
runtimeFilter.addEventListener('change', filterLeaderboard);
|
||||||
|
|
||||||
// Set up sorting
|
|
||||||
sortableHeaders.forEach(header => {
|
sortableHeaders.forEach(header => {
|
||||||
header.addEventListener('click', () => {
|
header.addEventListener('click', () => {
|
||||||
const column = header.dataset.sort;
|
const column = header.dataset.sort;
|
||||||
|
|
||||||
// Reset all sort indicators
|
sortableHeaders.forEach(h => h.classList.remove('sort-asc', 'sort-desc'));
|
||||||
sortableHeaders.forEach(h => {
|
|
||||||
h.classList.remove('sort-asc', 'sort-desc');
|
if (currentSort.column === column) {
|
||||||
|
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
currentSort.column = column;
|
||||||
|
currentSort.direction = 'asc';
|
||||||
|
}
|
||||||
|
|
||||||
|
header.classList.add(`sort-${currentSort.direction}`);
|
||||||
|
sortLeaderboard(column, currentSort.direction);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Determine new sort direction
|
|
||||||
if (currentSort.column === column) {
|
|
||||||
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
|
||||||
} else {
|
|
||||||
currentSort.column = column;
|
|
||||||
currentSort.direction = 'asc';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply new sort
|
|
||||||
header.classList.add(`sort-${currentSort.direction}`);
|
|
||||||
sortLeaderboard(column, currentSort.direction);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
const rankInfoBtn = document.getElementById('rankInfoBtn');
|
||||||
|
const rankingExplanation = document.getElementById('rankingExplanation');
|
||||||
|
|
||||||
|
rankInfoBtn.addEventListener('click', () => {
|
||||||
|
if (rankingExplanation.style.display === 'none' || rankingExplanation.style.display === '') {
|
||||||
|
rankingExplanation.style.display = 'block';
|
||||||
|
rankInfoBtn.classList.add('active');
|
||||||
|
} else {
|
||||||
|
rankingExplanation.style.display = 'none';
|
||||||
|
rankInfoBtn.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
253
src/utils.py
253
src/utils.py
@@ -5,8 +5,127 @@ import io
|
|||||||
import tempfile
|
import tempfile
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import ast
|
||||||
|
|
||||||
def run_code_against_tests(user_code, test_code):
|
# Security configuration
|
||||||
|
ALLOWED_IMPORTS = {
|
||||||
|
'math', 'random', 'datetime', 'json', 'collections', 'itertools',
|
||||||
|
'functools', 'operator', 'copy', 'unittest', 're', 'string'
|
||||||
|
}
|
||||||
|
|
||||||
|
DANGEROUS_PATTERNS = [
|
||||||
|
r'import\s+os(?:\s|$|\.)',
|
||||||
|
r'from\s+os\s+import',
|
||||||
|
r'import\s+subprocess(?:\s|$|\.)',
|
||||||
|
r'from\s+subprocess\s+import',
|
||||||
|
r'import\s+sys(?:\s|$|\.)',
|
||||||
|
r'from\s+sys\s+import',
|
||||||
|
r'import\s+shutil(?:\s|$|\.)',
|
||||||
|
r'from\s+shutil\s+import',
|
||||||
|
r'import\s+pathlib(?:\s|$|\.)',
|
||||||
|
r'from\s+pathlib\s+import',
|
||||||
|
r'__import__\s*\(',
|
||||||
|
r'exec\s*\(',
|
||||||
|
r'eval\s*\(',
|
||||||
|
r'compile\s*\(',
|
||||||
|
r'open\s*\(',
|
||||||
|
r'file\s*\(',
|
||||||
|
r'input\s*\(',
|
||||||
|
r'raw_input\s*\(',
|
||||||
|
r'\.unlink\s*\(',
|
||||||
|
r'\.remove\s*\(',
|
||||||
|
r'\.rmdir\s*\(',
|
||||||
|
r'\.rmtree\s*\(',
|
||||||
|
r'\.delete\s*\(',
|
||||||
|
r'\.kill\s*\(',
|
||||||
|
r'\.terminate\s*\(',
|
||||||
|
]
|
||||||
|
|
||||||
|
def validate_code_security(code):
|
||||||
|
"""
|
||||||
|
Validates code for security issues.
|
||||||
|
Returns (is_safe, error_message)
|
||||||
|
"""
|
||||||
|
# Check for dangerous patterns
|
||||||
|
for pattern in DANGEROUS_PATTERNS:
|
||||||
|
if re.search(pattern, code, re.IGNORECASE):
|
||||||
|
return False, f"Dangerous operation detected: {pattern}"
|
||||||
|
|
||||||
|
# Parse AST to check imports
|
||||||
|
try:
|
||||||
|
tree = ast.parse(code)
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.Import):
|
||||||
|
for alias in node.names:
|
||||||
|
module_name = alias.name.split('.')[0]
|
||||||
|
if module_name not in ALLOWED_IMPORTS:
|
||||||
|
return False, f"Import not allowed: {module_name}"
|
||||||
|
elif isinstance(node, ast.ImportFrom):
|
||||||
|
if node.module:
|
||||||
|
module_name = node.module.split('.')[0]
|
||||||
|
if module_name not in ALLOWED_IMPORTS:
|
||||||
|
return False, f"Import not allowed: {module_name}"
|
||||||
|
except SyntaxError as e:
|
||||||
|
return False, f"Syntax error in code: {str(e)}"
|
||||||
|
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
def create_restricted_globals():
|
||||||
|
"""Create a restricted global namespace for code execution."""
|
||||||
|
safe_builtins = {
|
||||||
|
'abs', 'all', 'any', 'bin', 'bool', 'chr', 'dict', 'dir',
|
||||||
|
'divmod', 'enumerate', 'filter', 'float', 'format', 'frozenset',
|
||||||
|
'hex', 'id', 'int', 'isinstance', 'issubclass', 'iter', 'len',
|
||||||
|
'list', 'map', 'max', 'min', 'next', 'oct', 'ord', 'pow',
|
||||||
|
'print', 'range', 'repr', 'reversed', 'round', 'set', 'slice',
|
||||||
|
'sorted', 'str', 'sum', 'tuple', 'type', 'zip'
|
||||||
|
}
|
||||||
|
|
||||||
|
restricted_globals = {
|
||||||
|
'__builtins__': {name: __builtins__[name] for name in safe_builtins if name in __builtins__}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add allowed modules
|
||||||
|
for module in ALLOWED_IMPORTS:
|
||||||
|
try:
|
||||||
|
restricted_globals[module] = __import__(module)
|
||||||
|
except ImportError:
|
||||||
|
pass # Module not available
|
||||||
|
|
||||||
|
return restricted_globals
|
||||||
|
|
||||||
|
def run_code_against_tests(user_code, test_code, max_execution_time=5):
|
||||||
|
"""
|
||||||
|
Securely run user code against test code with safety restrictions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_code: The user's solution code
|
||||||
|
test_code: The test code to validate the solution
|
||||||
|
max_execution_time: Maximum execution time in seconds (default: 5)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Result containing passed, output, runtime, and error information
|
||||||
|
"""
|
||||||
|
# Validate security for both user code and test code
|
||||||
|
user_safe, user_error = validate_code_security(user_code)
|
||||||
|
if not user_safe:
|
||||||
|
return {
|
||||||
|
'passed': False,
|
||||||
|
'output': '',
|
||||||
|
'runtime': 0,
|
||||||
|
'error': f"Security violation in user code: {user_error}"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_safe, test_error = validate_code_security(test_code)
|
||||||
|
if not test_safe:
|
||||||
|
return {
|
||||||
|
'passed': False,
|
||||||
|
'output': '',
|
||||||
|
'runtime': 0,
|
||||||
|
'error': f"Security violation in test code: {test_error}"
|
||||||
|
}
|
||||||
|
|
||||||
local_ns = {}
|
local_ns = {}
|
||||||
output = ''
|
output = ''
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
@@ -17,60 +136,83 @@ def run_code_against_tests(user_code, test_code):
|
|||||||
try:
|
try:
|
||||||
# Check if unittest is used in test_code
|
# Check if unittest is used in test_code
|
||||||
if 'unittest' in test_code:
|
if 'unittest' in test_code:
|
||||||
# Write user code + test code to a temp file
|
# Create temp file in a secure temporary directory
|
||||||
with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False, encoding='utf-8') as f:
|
temp_dir = tempfile.mkdtemp(prefix='secure_code_')
|
||||||
combined_code = f"{user_code}\n\n{test_code}"
|
|
||||||
f.write(combined_code)
|
|
||||||
f.flush()
|
|
||||||
temp_file = f.name
|
|
||||||
|
|
||||||
# Run the file as a subprocess
|
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
temp_file = os.path.join(temp_dir, 'test_code.py')
|
||||||
[sys.executable, temp_file],
|
combined_code = f"{user_code}\n\n{test_code}"
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10,
|
|
||||||
encoding='utf-8'
|
|
||||||
)
|
|
||||||
output = proc.stdout
|
|
||||||
if proc.stderr:
|
|
||||||
output += f"\n{proc.stderr}"
|
|
||||||
|
|
||||||
passed = proc.returncode == 0
|
# Write to temp file with restricted permissions
|
||||||
if not passed:
|
with open(temp_file, 'w', encoding='utf-8') as f:
|
||||||
error = f"Tests failed. Return code: {proc.returncode}\n{output}"
|
f.write(combined_code)
|
||||||
else:
|
os.chmod(temp_file, 0o600) # Read/write for owner only
|
||||||
# For successful unittest runs, the stderr contains the test results
|
|
||||||
if proc.stderr and "OK" in proc.stderr:
|
# Run the file as a subprocess with additional security
|
||||||
output = proc.stderr # Use stderr as the main output for unittest
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, temp_file],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=max_execution_time,
|
||||||
|
encoding='utf-8',
|
||||||
|
cwd=temp_dir, # Run in the temporary directory
|
||||||
|
env={'PYTHONPATH': ''} # Restrict Python path
|
||||||
|
)
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
# Combine both stdout and stderr to capture all output
|
||||||
passed = False
|
combined_output = ""
|
||||||
error = "Code execution timed out after 10 seconds"
|
if proc.stdout:
|
||||||
output = "Execution timed out"
|
combined_output += proc.stdout
|
||||||
|
if proc.stderr:
|
||||||
|
if combined_output:
|
||||||
|
combined_output += "\n" + proc.stderr
|
||||||
|
else:
|
||||||
|
combined_output = proc.stderr
|
||||||
|
|
||||||
|
output = combined_output
|
||||||
|
passed = proc.returncode == 0
|
||||||
|
|
||||||
|
if not passed:
|
||||||
|
error = f"Tests failed. Return code: {proc.returncode}\n{output}"
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
passed = False
|
||||||
|
error = f"Code execution timed out after {max_execution_time} seconds"
|
||||||
|
output = "Execution timed out"
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Secure cleanup of temporary directory and files
|
||||||
|
try:
|
||||||
|
if temp_file and os.path.exists(temp_file):
|
||||||
|
os.chmod(temp_file, 0o600) # Ensure we can delete
|
||||||
|
os.unlink(temp_file)
|
||||||
|
if os.path.exists(temp_dir):
|
||||||
|
os.rmdir(temp_dir)
|
||||||
|
except Exception as cleanup_error:
|
||||||
|
print(f"Warning: Could not clean up temp files: {cleanup_error}")
|
||||||
else:
|
else:
|
||||||
# Capture stdout
|
# Direct execution with restricted globals
|
||||||
old_stdout = sys.stdout
|
old_stdout = sys.stdout
|
||||||
captured_output = io.StringIO()
|
captured_output = io.StringIO()
|
||||||
sys.stdout = captured_output
|
sys.stdout = captured_output
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Execute user code
|
# Create restricted execution environment
|
||||||
exec(user_code, {}, local_ns)
|
restricted_globals = create_restricted_globals()
|
||||||
|
|
||||||
|
# Execute user code in restricted environment
|
||||||
|
exec(user_code, restricted_globals, local_ns)
|
||||||
|
|
||||||
# Execute test code (should raise AssertionError if fail)
|
# Execute test code (should raise AssertionError if fail)
|
||||||
exec(test_code, local_ns, local_ns)
|
exec(test_code, {**restricted_globals, **local_ns}, local_ns)
|
||||||
passed = True
|
passed = True
|
||||||
|
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
passed = False
|
passed = False
|
||||||
error = f"Assertion failed: {str(e)}"
|
error = f"Assertion failed: {str(e)}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
passed = False
|
passed = False
|
||||||
error = f"Runtime error: {traceback.format_exc()}"
|
error = f"Runtime error: {traceback.format_exc()}"
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
output = captured_output.getvalue()
|
output = captured_output.getvalue()
|
||||||
sys.stdout = old_stdout
|
sys.stdout = old_stdout
|
||||||
@@ -78,17 +220,14 @@ def run_code_against_tests(user_code, test_code):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
passed = False
|
passed = False
|
||||||
error = f"Execution error: {traceback.format_exc()}"
|
error = f"Execution error: {traceback.format_exc()}"
|
||||||
|
|
||||||
finally:
|
|
||||||
# Clean up temporary file
|
|
||||||
if temp_file and os.path.exists(temp_file):
|
|
||||||
try:
|
|
||||||
os.unlink(temp_file)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Warning: Could not delete temp file {temp_file}: {e}")
|
|
||||||
|
|
||||||
runtime = time.perf_counter() - start
|
runtime = time.perf_counter() - start
|
||||||
|
|
||||||
|
# Limit output size to prevent memory issues
|
||||||
|
max_output_size = 10000 # 10KB limit
|
||||||
|
if len(output) > max_output_size:
|
||||||
|
output = output[:max_output_size] + "\n... (output truncated)"
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
'passed': passed,
|
'passed': passed,
|
||||||
'output': output.strip() if output else '',
|
'output': output.strip() if output else '',
|
||||||
@@ -97,3 +236,27 @@ def run_code_against_tests(user_code, test_code):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
# Example usage with additional safety wrapper
|
||||||
|
def safe_code_runner(user_code, test_code):
|
||||||
|
"""
|
||||||
|
Additional wrapper for extra safety checks.
|
||||||
|
"""
|
||||||
|
# Additional length checks
|
||||||
|
if len(user_code) > 50000: # 50KB limit
|
||||||
|
return {
|
||||||
|
'passed': False,
|
||||||
|
'output': '',
|
||||||
|
'runtime': 0,
|
||||||
|
'error': "User code too large (maximum 50KB allowed)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(test_code) > 10000: # 10KB limit for test code
|
||||||
|
return {
|
||||||
|
'passed': False,
|
||||||
|
'output': '',
|
||||||
|
'runtime': 0,
|
||||||
|
'error': "Test code too large (maximum 10KB allowed)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return run_code_against_tests(user_code, test_code)
|
||||||
Reference in New Issue
Block a user