Skip to content

Commit acb34ee

Browse files
feat: problem stats
1 parent f1b083a commit acb34ee

File tree

3 files changed

+73
-83
lines changed

3 files changed

+73
-83
lines changed

src/commands/show.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
solution_manager = SolutionManager(auth_manager.get_session())
99
console = Console()
1010

11-
def show(problem: str = typer.Argument(..., help="Problem slug (e.g., 'two-sum')")):
11+
def show(problem: str = typer.Argument(..., help="Problem slug or number (e.g., 'two-sum' or '1')"), layout: bool = False):
1212
"""Show problem details including description and test cases"""
13+
1314
if not auth_manager.is_authenticated:
1415
typer.echo(typer.style("❌ Please login first using the login command", fg=typer.colors.RED))
1516
raise typer.Exit(1)
@@ -19,4 +20,7 @@ def show(problem: str = typer.Argument(..., help="Problem slug (e.g., 'two-sum')
1920
typer.echo(typer.style(f"❌ Problem '{problem}' not found", fg=typer.colors.RED))
2021
raise typer.Exit(1)
2122

22-
ProblemDetails(data.get('data', {}).get('question')).display()
23+
if not layout:
24+
ProblemDetails(data.get('data', {}).get('question')).display_full()
25+
else:
26+
ProblemDetails(data.get('data', {}).get('question')).display()

src/lib/problem_ui.py

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
from rich.console import Console
2-
from rich.table import Table
32
from rich.panel import Panel
43
from rich.markdown import Markdown
54
from rich.layout import Layout
65
from rich.box import ROUNDED
7-
from rich.style import Style
8-
from rich.text import Text
9-
from rich.align import Align
106
from bs4 import BeautifulSoup, Tag
117
import json
128
from dataclasses import dataclass
139
from typing import List, Dict, Optional
1410

15-
import typer
16-
1711
console = Console()
1812

1913
@dataclass
@@ -47,9 +41,13 @@ def __init__(self, problem_data: dict):
4741
except Exception:
4842
pass
4943

50-
self.total_accepted: int = problem_data.get('totalAccepted', 0)
51-
self.total_submissions: int = problem_data.get('totalSubmissions', 0)
52-
self.acceptance_rate: float = problem_data.get('acRate', 0)
44+
# Parse stats from JSON string
45+
self.stats = {}
46+
try:
47+
stats_str = problem_data.get('stats', '{}')
48+
self.stats = json.loads(stats_str)
49+
except Exception:
50+
self.stats = {}
5351

5452
self._parse_content()
5553
self.console_width = console.width
@@ -85,36 +83,6 @@ def _parse_content(self):
8583
else:
8684
current_section.append(text)
8785

88-
@property
89-
def available_languages(self) -> List[str]:
90-
"""Get list of available programming languages"""
91-
return [snippet['lang'] for snippet in self.code_snippets]
92-
93-
@property
94-
def formatted_function_signature(self) -> str:
95-
"""Get the formatted function signature"""
96-
if not self.function_metadata:
97-
return "[red]Error loading function signature[/]"
98-
99-
param_str = ', '.join(
100-
f"{p['name']}: {p['type']}"
101-
for p in self.function_metadata.params
102-
)
103-
104-
return (
105-
"[bold blue]Function Signature:[/]\n"
106-
f"[bold cyan]def[/] [yellow]{self.function_metadata.name}[/](\n"
107-
f" [green]{param_str}[/]\n"
108-
f") -> [green]{self.function_metadata.return_type}[/]"
109-
)
110-
111-
def get_code_snippet(self, language: str) -> Optional[str]:
112-
"""Get code snippet for specific language"""
113-
for snippet in self.code_snippets:
114-
if snippet['lang'].lower() == language.lower():
115-
return snippet['code']
116-
return None
117-
11886
def format_test_case(self, input_str: str, expected_str: str, case_num: int) -> str:
11987
return (
12088
f"[bold blue]Ex {case_num}:[/] "
@@ -220,15 +188,15 @@ def _format_test_cases(self):
220188

221189
def _format_stats(self) -> str:
222190
"""Format problem statistics"""
223-
acceptance_rate = f"{self.acceptance_rate:.1f}%" if self.acceptance_rate else "N/A"
224-
total_accepted = f"{self.total_accepted:,}" if self.total_accepted else "N/A"
225-
total_submissions = f"{self.total_submissions:,}" if self.total_submissions else "N/A"
191+
accepted = self.stats.get('totalAccepted', 'N/A')
192+
submissions = self.stats.get('totalSubmission', 'N/A')
193+
ac_rate = self.stats.get('acRate', 'N/A')
226194

227195
return (
228196
"[bold blue]Problem Stats[/]\n\n"
229-
f"[cyan]Acceptance Rate:[/] {acceptance_rate}\n"
230-
f"[cyan]Total Accepted:[/] {total_accepted}\n"
231-
f"[cyan]Total Submissions:[/] {total_submissions}"
197+
f"[cyan]Acceptance Rate:[/] {ac_rate}\n"
198+
f"[cyan]Total Accepted:[/] {accepted}\n"
199+
f"[cyan]Total Submissions:[/] {submissions}"
232200
)
233201

234202
def display(self):
@@ -281,4 +249,39 @@ def display(self):
281249
padding=(1, 2)
282250
))
283251

284-
console.print(layout)
252+
console.print(layout)
253+
254+
def display_only_description(self):
255+
"""Display only the problem description"""
256+
console.clear()
257+
console.print()
258+
259+
console.print(
260+
Panel(
261+
Markdown(self._format_description(self.content)),
262+
box=ROUNDED,
263+
title=str(self._create_header()),
264+
border_style="blue",
265+
padding=(1, 2)
266+
)
267+
)
268+
269+
def display_only_test_cases(self):
270+
"""Display only the test cases"""
271+
console.clear()
272+
console.print()
273+
274+
console.print(
275+
Panel(
276+
self._format_test_cases(),
277+
box=ROUNDED,
278+
title="[bold blue]Examples",
279+
border_style="blue",
280+
padding=(1, 2)
281+
)
282+
)
283+
284+
def display_full(self):
285+
"""Display the full problem details"""
286+
self.display_only_description()
287+
self.display_only_test_cases()

src/server/solution_manager.py

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
1-
import requests
21
from typing import Dict, Any
3-
import json
42
import time
5-
from bs4 import BeautifulSoup
6-
import html
7-
8-
import typer
93

104
class SolutionManager:
115
def __init__(self, session):
126
self.session = session
137
self.BASE_URL = "https://leetcode.com"
148

15-
def get_question_data(self, title_slug: str) -> Dict[str, Any]:
16-
"""Get question details using GraphQL"""
9+
def get_question_data(self, question_identifier: str) -> Dict[str, Any]:
10+
"""Get question details using GraphQL
11+
Args:
12+
question_identifier: Can be either title slug (e.g. 'two-sum') or question number (e.g. '1')
13+
"""
14+
if question_identifier.isdigit():
15+
response = self.session.get(f"{self.BASE_URL}/api/problems/all/")
16+
if response.status_code == 200:
17+
problems = response.json().get('stat_status_pairs', [])
18+
for problem in problems:
19+
if str(problem['stat']['frontend_question_id']) == question_identifier:
20+
question_identifier = problem['stat']['question__title_slug']
21+
break
22+
else:
23+
return {"error": f"Question number {question_identifier} not found"}
24+
1725
query = """
1826
query questionData($titleSlug: String!) {
1927
question(titleSlug: $titleSlug) {
@@ -24,6 +32,7 @@ def get_question_data(self, title_slug: str) -> Dict[str, Any]:
2432
difficulty
2533
exampleTestcaseList
2634
sampleTestCase
35+
stats
2736
metaData
2837
codeSnippets {
2938
lang
@@ -38,38 +47,12 @@ def get_question_data(self, title_slug: str) -> Dict[str, Any]:
3847
f"{self.BASE_URL}/graphql",
3948
json={
4049
"query": query,
41-
"variables": {"titleSlug": title_slug}
50+
"variables": {"titleSlug": question_identifier}
4251
}
4352
)
4453

4554
return response.json()
4655

47-
def format_problem_details(self, data: Dict[str, Any]) -> Dict[str, Any]:
48-
"""Format problem details for display"""
49-
question = data['data']['question']
50-
51-
# Parse HTML content
52-
soup = BeautifulSoup(question['content'], 'html.parser')
53-
content = soup.get_text('\n').strip()
54-
55-
# Parse metadata
56-
metadata = json.loads(question.get('metaData', '{}'))
57-
58-
# Get example test cases
59-
example_tests = question.get('exampleTestcases', '').split('\n')
60-
example_tests = [test for test in example_tests if test]
61-
62-
return {
63-
'id': question['questionId'],
64-
'title': question['title'],
65-
'difficulty': question['difficulty'],
66-
'content': content,
67-
'example_tests': example_tests,
68-
'params': metadata.get('params', []),
69-
'return_type': metadata.get('return', {}).get('type', 'Unknown'),
70-
'code_snippets': question['codeSnippets']
71-
}
72-
7356
def submit_solution(self, title_slug: str, code: str, lang: str = "python3") -> Dict[str, Any]:
7457
"""Submit a solution to LeetCode"""
7558
# First get the question ID

0 commit comments

Comments
 (0)