File size: 5,252 Bytes
5f7afef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Dict, List, Optional
from codestar.core.schemas import ProblemMeta

DEFAULT_PROBLEMS_DIR = Path(__file__).resolve().parents[2] / "data" / "problems"


class ProblemRepository:
    def __init__(self, problems_dir: Path = DEFAULT_PROBLEMS_DIR):
        self.problems_dir = problems_dir
        self._cache: Dict[str, ProblemMeta] = {}
        self._list_items_cache: List[Dict[str, Any]] = []
        self._by_category_cache: Dict[str, List[Dict[str, Any]]] = {}
        self._featured_cache: List[Dict[str, Any]] = []
        self._difficulty_breakdowns: Dict[str, Dict[str, int]] = {}
        self._test_cases: Dict[str, List[Dict[str, Any]]] = {}
        self.reload()

    def reload(self):
        self._cache.clear()
        self._list_items_cache.clear()
        self._by_category_cache.clear()
        self._featured_cache.clear()
        self._difficulty_breakdowns.clear()
        self._test_cases.clear()

        if not self.problems_dir.exists():
            self.problems_dir.mkdir(parents=True, exist_ok=True)
            return

        for path in sorted(self.problems_dir.glob("*.json")):
            try:
                data = json.loads(path.read_text(encoding="utf-8"))
                problem = ProblemMeta(**data)
                self._cache[problem.slug] = problem

                # Extract 2D difficulty breakdown with fallback calibration
                breakdown = data.get("difficulty_breakdown")
                if not breakdown or not isinstance(breakdown, dict):
                    diff_str = str(problem.difficulty).capitalize()
                    if diff_str == "Easy":
                        breakdown = {"concept": 1, "implementation": 1}
                    elif diff_str == "Hard":
                        breakdown = {"concept": 4, "implementation": 4}
                    else:
                        breakdown = {"concept": 3, "implementation": 3}
                self._difficulty_breakdowns[problem.slug] = breakdown
                object.__setattr__(problem, "difficulty_breakdown", breakdown)

                # Extract test_cases contract
                tcases = data.get("test_cases", [])
                self._test_cases[problem.slug] = tcases
                object.__setattr__(problem, "test_cases", tcases)
                
                # Pre-construct list item representation for high-speed API responses
                item_dict = {
                    "slug": problem.slug,
                    "title": problem.title,
                    "difficulty": problem.difficulty,
                    "difficulty_breakdown": breakdown,
                    "category": problem.category,
                    "tags": problem.tags,
                    "time_limit": problem.time_limit,
                    "memory_limit_mb": problem.memory_limit_mb,
                    "is_featured": problem.is_featured,
                }
                self._list_items_cache.append(item_dict)

                cat_key = problem.category.lower()
                if cat_key not in self._by_category_cache:
                    self._by_category_cache[cat_key] = []
                self._by_category_cache[cat_key].append(item_dict)

                if problem.is_featured:
                    self._featured_cache.append(item_dict)

            except Exception as e:
                print(f"[Warning] Failed to load problem from {path}: {e}")

    def get_list_items(self, category: Optional[str] = None) -> List[Dict[str, Any]]:
        if not category or category.strip() in ("", "全部"):
            return self._list_items_cache
        cat_lower = category.lower().strip()
        if cat_lower in ("featured", "常见手撕", "高频手撕", "🔥 常见手撕"):
            return self._featured_cache
        return self._by_category_cache.get(cat_lower, [])

    def get(self, slug: str) -> Optional[ProblemMeta]:
        return self._cache.get(slug)

    def get_difficulty_breakdown(self, slug: str) -> Optional[Dict[str, int]]:
        return self._difficulty_breakdowns.get(slug)

    def get_test_cases(self, slug: str) -> List[Dict[str, Any]]:
        return self._test_cases.get(slug, [])

    def list(self) -> List[ProblemMeta]:
        return list(self._cache.values())

    def save(self, problem: ProblemMeta):
        self.problems_dir.mkdir(parents=True, exist_ok=True)
        file_path = self.problems_dir / f"{problem.slug}.json"
        data = problem.model_dump()
        if hasattr(problem, "difficulty_breakdown") and problem.difficulty_breakdown:
            data["difficulty_breakdown"] = problem.difficulty_breakdown
        if hasattr(problem, "test_cases") and problem.test_cases:
            data["test_cases"] = problem.test_cases
        file_path.write_text(
            json.dumps(data, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        self._cache[problem.slug] = problem
        if hasattr(problem, "difficulty_breakdown"):
            self._difficulty_breakdowns[problem.slug] = problem.difficulty_breakdown
        if hasattr(problem, "test_cases"):
            self._test_cases[problem.slug] = problem.test_cases


repo = ProblemRepository()