Skip to content

Commit 01fb8a2

Browse files
committed
feat: add script to sort sets by difficulty
1 parent fed15cb commit 01fb8a2

File tree

4 files changed

+321
-97
lines changed

4 files changed

+321
-97
lines changed

data/book-sets.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,32 @@
1717
"Dynamic Programming"
1818
],
1919
"problems": [
20-
724, 392, 205, 142, 21, 206, 876, 230, 98, 435, 452, 704, 701, 235, 208, 746, 198, 64, 494, 733, 994, 547, 334, 238, 1431, 345, 1732, 605, 338,
21-
918, 1071, 1768, 151, 643, 1004, 11, 394, 841, 104, 116, 19, 1372, 136, 283, 167, 121, 66, 1480, 1704, 1657, 120, 278, 74, 153, 933, 1046, 215,
22-
383, 200, 997, 53, 509, 409, 1926, 1493, 139, 1137, 2462, 102, 199, 450, 15, 1557, 1143
20+
104, 136, 206, 283, 338, 345, 374, 392, 605, 643, 700, 724, 746, 872, 933, 1071, 1137, 1207, 1431, 1732, 1768, 2215, 11, 17, 62, 72, 151, 162,
21+
198, 199, 208, 215, 216, 236, 238, 328, 334, 394, 399, 435, 437, 443, 450, 452, 547, 649, 714, 735, 739, 790, 841, 875, 901, 994, 1004, 1143,
22+
1161, 1268, 1318, 1372, 1448, 1456, 1466, 1493, 1657, 1679, 1926, 2095, 2130, 2300, 2336, 2352, 2390, 2462, 2542
2323
]
2424
},
2525
{
2626
"title": "LeetCode Top 150",
2727
"description": "Problem list from official https://leetcode.com/studyplan/top-interview-150",
2828
"tags": [],
2929
"problems": [
30-
88, 27, 26, 80, 169, 189, 121, 122, 55, 45, 274, 380, 238, 134, 135, 42, 13, 12, 58, 14, 151, 6, 28, 68, 125, 392, 167, 11, 15, 209, 3, 30, 76,
31-
36, 54, 48, 73, 289, 383, 205, 290, 242, 49, 1, 202, 219, 128, 228, 56, 57, 452, 20, 71, 155, 150, 224, 141, 2, 21, 138, 92, 25, 19, 82, 61, 86,
32-
146, 104, 100, 226, 101, 105, 106, 117, 114, 112, 129, 124, 173, 222, 236, 199, 637, 102, 103, 530, 230, 98, 200, 130, 133, 399, 207, 210, 909,
33-
433, 127, 208, 211, 212, 17, 77, 46, 39, 52, 22, 79, 108, 148, 427, 23, 53, 918, 35, 74, 162, 33, 34, 153, 4, 215, 502, 373, 295, 67, 190, 191,
34-
136, 137, 201, 9, 66, 172, 69, 50, 149, 70, 198, 139, 322, 300, 120, 64, 63, 5, 97, 72, 123, 188, 221
30+
1, 9, 13, 14, 20, 21, 26, 27, 28, 35, 58, 66, 67, 69, 70, 88, 100, 101, 104, 108, 112, 121, 125, 136, 141, 169, 190, 191, 202, 205, 219, 222,
31+
226, 228, 242, 290, 383, 392, 530, 637, 2, 3, 5, 6, 11, 12, 15, 17, 19, 22, 33, 34, 36, 39, 45, 46, 48, 49, 50, 53, 54, 55, 56, 57, 61, 63, 64,
32+
71, 72, 73, 74, 77, 79, 80, 82, 86, 92, 97, 98, 102, 103, 105, 106, 114, 117, 120, 122, 128, 129, 130, 133, 134, 137, 138, 139, 146, 148, 150,
33+
151, 153, 155, 162, 167, 172, 173, 189, 198, 199, 200, 201, 207, 208, 209, 210, 211, 215, 221, 230, 236, 238, 274, 289, 300, 322, 373, 380, 399,
34+
427, 433, 452, 909, 918, 4, 23, 25, 30, 42, 52, 68, 76, 123, 124, 127, 135, 149, 188, 212, 224, 295, 502
3535
]
3636
},
3737
{
3838
"title": "Top 100 Liked Questions",
3939
"description": "Problem list from official https://leetcode.com/studyplan/top-100-liked/",
4040
"tags": ["Essential", "High-Value Interview Prep"],
4141
"problems": [
42-
1, 2, 3, 4, 5, 7, 11, 15, 17, 19, 20, 21, 23, 31, 33, 34, 39, 42, 46, 48, 49, 53, 56, 62, 64, 70, 72, 75, 76, 78, 79, 84, 94, 98, 101, 102, 104,
43-
105, 114, 121, 124, 128, 136, 139, 141, 146, 148, 155, 160, 169, 190, 198, 199, 200, 206, 207, 208, 215, 221, 230, 234, 236, 238, 240, 242, 287,
44-
297, 300, 322, 337, 344, 347, 378, 412, 438, 448, 460, 494, 543, 560, 581, 617, 647, 695, 739, 876, 973, 981, 994, 1029, 1046, 1167, 1249, 1288,
45-
1304, 1337, 1423, 1480, 1528, 1584, 1672
42+
1, 20, 21, 70, 94, 101, 104, 121, 136, 141, 160, 169, 190, 206, 234, 242, 344, 412, 448, 543, 617, 876, 1046, 1304, 1337, 1480, 1528, 1672, 2,
43+
3, 5, 7, 11, 15, 17, 19, 31, 33, 34, 39, 46, 48, 49, 53, 56, 62, 64, 72, 75, 78, 79, 98, 102, 105, 114, 128, 139, 146, 148, 155, 198, 199, 200,
44+
207, 208, 215, 221, 230, 236, 238, 240, 287, 300, 322, 337, 347, 378, 438, 494, 560, 581, 647, 695, 739, 973, 981, 994, 1029, 1167, 1249, 1288,
45+
1423, 1584, 4, 23, 42, 76, 84, 124, 297, 460
4646
]
4747
},
4848
{

scripts/normalize_book_sets.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ def normalize_book_sets(
147147

148148
if removed:
149149
changes_made = True
150-
print(f"\n[All-TODO] Removing {len(removed)} problems with both solution and explanation:")
150+
print(
151+
f"\n[All-TODO] Removing {len(removed)} problems with both solution and explanation:"
152+
)
151153
print(f" Removed: {removed[:10]}{'...' if len(removed) > 10 else ''}")
152154
all_todo_obj["problems"] = sorted(new_problems)
153155
print(f" Updated count: {original_count} -> {len(new_problems)}")
@@ -190,9 +192,13 @@ def normalize_book_sets(
190192

191193
print(f"\n[All] Updating problem list:")
192194
if added:
193-
print(f" Added {len(added)} problems: {added[:10]}{'...' if len(added) > 10 else ''}")
195+
print(
196+
f" Added {len(added)} problems: {added[:10]}{'...' if len(added) > 10 else ''}"
197+
)
194198
if removed:
195-
print(f" Removed {len(removed)} problems: {removed[:10]}{'...' if len(removed) > 10 else ''}")
199+
print(
200+
f" Removed {len(removed)} problems: {removed[:10]}{'...' if len(removed) > 10 else ''}"
201+
)
196202
print(f" Updated count: {original_count} -> {len(problems_with_both)}")
197203

198204
all_obj["problems"] = problems_with_both
@@ -238,7 +244,9 @@ def normalize_book_sets(
238244
print(f" Status: COMPLETE ({completed}/{total} problems)")
239245
else:
240246
print(f"\n{title}")
241-
print(f" Status: INCOMPLETE ({completed}/{total} problems, {completion_pct:.1f}%)")
247+
print(
248+
f" Status: INCOMPLETE ({completed}/{total} problems, {completion_pct:.1f}%)"
249+
)
242250
if missing:
243251
# Show first 20 missing problems, then count if more
244252
if len(missing) <= 20:
@@ -257,9 +265,17 @@ def normalize_book_sets(
257265
print("SUMMARY")
258266
print("=" * 70)
259267
print(f"Total sets: {total_sets}")
260-
print(f"Completed sets: {completed_sets} ({completed_sets/total_sets*100:.1f}%)" if total_sets > 0 else "Completed sets: 0")
268+
print(
269+
f"Completed sets: {completed_sets} ({completed_sets/total_sets*100:.1f}%)"
270+
if total_sets > 0
271+
else "Completed sets: 0"
272+
)
261273
print(f"Total problems across all sets: {total_problems}")
262-
print(f"Completed problems: {total_completed_problems} ({total_completed_problems/total_problems*100:.1f}%)" if total_problems > 0 else "Completed problems: 0")
274+
print(
275+
f"Completed problems: {total_completed_problems} ({total_completed_problems/total_problems*100:.1f}%)"
276+
if total_problems > 0
277+
else "Completed problems: 0"
278+
)
263279

264280
# Write the updated JSON file
265281
if changes_made:
@@ -322,4 +338,3 @@ def normalize_book_sets(
322338
args.explanations_dir,
323339
dry_run=dry_run,
324340
)
325-
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script to sort problems in book-sets.json by difficulty (Easy -> Medium -> Hard).
4+
5+
By default, sorts:
6+
- "LeetCode 75"
7+
- "LeetCode Top 150"
8+
- "Top 100 Liked Questions"
9+
10+
Usage:
11+
python scripts/sort_book_sets_by_difficulty.py
12+
python scripts/sort_book_sets_by_difficulty.py --all # Sort all sets
13+
python scripts/sort_book_sets_by_difficulty.py --set "LeetCode 75" # Sort specific set
14+
"""
15+
16+
import json
17+
import os
18+
import sys
19+
import argparse
20+
from pathlib import Path
21+
22+
# Paths
23+
SCRIPT_DIR = Path(__file__).parent
24+
ROOT_DIR = SCRIPT_DIR.parent
25+
BOOK_SETS_PATH = ROOT_DIR / "data" / "book-sets.json"
26+
LEETCODE_PROBLEMS_PATH = ROOT_DIR / "data" / "leetcode-problems.json"
27+
28+
# Default sets to sort
29+
DEFAULT_SETS = ["LeetCode 75", "LeetCode Top 150", "Top 100 Liked Questions"]
30+
31+
# Difficulty order for sorting
32+
DIFFICULTY_ORDER = {"Easy": 0, "Medium": 1, "Hard": 2, "Unknown": 3}
33+
34+
35+
def load_json_file(file_path):
36+
"""Load JSON file."""
37+
try:
38+
with open(file_path, "r", encoding="utf-8") as f:
39+
return json.load(f)
40+
except FileNotFoundError:
41+
print(f"Error: File not found: {file_path}")
42+
sys.exit(1)
43+
except json.JSONDecodeError as e:
44+
print(f"Error: Invalid JSON in {file_path}: {e}")
45+
sys.exit(1)
46+
47+
48+
def get_problem_difficulty(problem_id, problems_data):
49+
"""Get difficulty for a problem ID."""
50+
problem_str = str(problem_id)
51+
if problem_str in problems_data:
52+
return problems_data[problem_str].get("difficulty", "Unknown")
53+
return "Unknown"
54+
55+
56+
def sort_problems_by_difficulty(problem_ids, problems_data):
57+
"""Sort problem IDs by difficulty (Easy -> Medium -> Hard)."""
58+
# Create list of (problem_id, difficulty_order)
59+
problems_with_order = []
60+
for pid in problem_ids:
61+
difficulty = get_problem_difficulty(pid, problems_data)
62+
order = DIFFICULTY_ORDER.get(difficulty, 3)
63+
problems_with_order.append((pid, order, difficulty))
64+
65+
# Sort by difficulty order, then by problem ID (for stable sorting within same difficulty)
66+
problems_with_order.sort(key=lambda x: (x[1], x[0]))
67+
68+
# Return sorted problem IDs
69+
return [pid for pid, _, _ in problems_with_order]
70+
71+
72+
def sort_book_set(book_set, problems_data, verbose=False):
73+
"""Sort problems in a book set by difficulty."""
74+
title = book_set.get("title", "Unknown")
75+
problems = book_set.get("problems", [])
76+
77+
if not problems:
78+
if verbose:
79+
print(f" {title}: No problems to sort")
80+
return False
81+
82+
# Sort problems
83+
sorted_problems = sort_problems_by_difficulty(problems, problems_data)
84+
85+
# Check if order changed
86+
if sorted_problems == problems:
87+
if verbose:
88+
print(f" {title}: Already sorted ({len(problems)} problems)")
89+
return False
90+
91+
# Update the set
92+
book_set["problems"] = sorted_problems
93+
94+
if verbose:
95+
# Show difficulty breakdown
96+
easy = sum(
97+
1
98+
for pid in sorted_problems
99+
if get_problem_difficulty(pid, problems_data) == "Easy"
100+
)
101+
medium = sum(
102+
1
103+
for pid in sorted_problems
104+
if get_problem_difficulty(pid, problems_data) == "Medium"
105+
)
106+
hard = sum(
107+
1
108+
for pid in sorted_problems
109+
if get_problem_difficulty(pid, problems_data) == "Hard"
110+
)
111+
unknown = len(sorted_problems) - easy - medium - hard
112+
113+
print(f" {title}: Sorted {len(sorted_problems)} problems")
114+
print(
115+
f" Easy: {easy}, Medium: {medium}, Hard: {hard}"
116+
+ (f", Unknown: {unknown}" if unknown > 0 else "")
117+
)
118+
print(f" First: {sorted_problems[0]}, Last: {sorted_problems[-1]}")
119+
120+
return True
121+
122+
123+
def main():
124+
parser = argparse.ArgumentParser(
125+
description="Sort problems in book-sets.json by difficulty (Easy -> Medium -> Hard)"
126+
)
127+
parser.add_argument(
128+
"--all",
129+
action="store_true",
130+
help="Sort all book sets (default: only LeetCode 75, Top 150, and Top 100 Liked)",
131+
)
132+
parser.add_argument("--set", type=str, help="Sort a specific book set by title")
133+
parser.add_argument(
134+
"--dry-run",
135+
action="store_true",
136+
help="Show what would be sorted without making changes",
137+
)
138+
parser.add_argument(
139+
"--verbose", "-v", action="store_true", help="Show detailed output"
140+
)
141+
142+
args = parser.parse_args()
143+
144+
# Load data
145+
print("Loading data files...")
146+
book_sets = load_json_file(BOOK_SETS_PATH)
147+
problems_data = load_json_file(LEETCODE_PROBLEMS_PATH)
148+
149+
# Determine which sets to sort
150+
if args.set:
151+
sets_to_sort = [args.set]
152+
elif args.all:
153+
sets_to_sort = [
154+
book_set["title"] for book_set in book_sets if "problems" in book_set
155+
]
156+
else:
157+
sets_to_sort = DEFAULT_SETS
158+
159+
print(f"\nSorting {len(sets_to_sort)} book set(s)...")
160+
if args.verbose:
161+
print(f"Sets to sort: {', '.join(sets_to_sort)}")
162+
print()
163+
164+
# Sort each set
165+
updated_count = 0
166+
for book_set in book_sets:
167+
title = book_set.get("title", "Unknown")
168+
169+
if title not in sets_to_sort:
170+
continue
171+
172+
if args.verbose or args.dry_run:
173+
print(f"Processing: {title}")
174+
175+
if sort_book_set(book_set, problems_data, verbose=args.verbose or args.dry_run):
176+
updated_count += 1
177+
178+
if args.verbose or args.dry_run:
179+
print()
180+
181+
# Save if not dry run
182+
if args.dry_run:
183+
print(f"Dry run complete. Would update {updated_count} set(s).")
184+
else:
185+
if updated_count > 0:
186+
# Save updated book-sets.json
187+
with open(BOOK_SETS_PATH, "w", encoding="utf-8") as f:
188+
json.dump(book_sets, f, indent=2)
189+
print(f"✓ Updated {updated_count} book set(s) in {BOOK_SETS_PATH}")
190+
else:
191+
print("No changes needed. All sets are already sorted.")
192+
193+
194+
if __name__ == "__main__":
195+
main()

0 commit comments

Comments
 (0)