#!/usr/bin/env python3 """Auto-generate Anki flashcards from problem .org files. Generates one recognition card per problem: Front: Problem number + name Back: Problem statement (first paragraph) """ import re import sys from pathlib import Path BASE = Path(__file__).parent DSA = BASE / "dsa" OUTPUT = BASE / "toolkit" / "gen-problem-cards.org" TOPIC_DISPLAY = { "1-d-dynamic-programming": "1D DP", "2-d-dynamic-programming": "2D DP", "advanced-graphs": "Advanced Graphs", "arrays-hashing": "Arrays & Hashing", "backtracking": "Backtracking", "binary-search": "Binary Search", "bit-manipulation": "Bit Manipulation", "graphs": "Graphs", "greedy": "Greedy", "heap-priority-queue": "Heap / Priority Queue", "intervals": "Intervals", "linked-list": "Linked List", "math-geometry": "Math & Geometry", "sliding-window": "Sliding Window", "stack": "Stack", "trees": "Trees", "tries": "Tries", "two-pointers": "Two Pointers", } def parse_file(path: Path) -> dict | None: """Extract problem metadata and statement from an .org file.""" text = path.read_text() # Match heading: * TODO 0217. Contains Duplicate :easy: m = re.search( r"^\* (?:TODO|DONE) (\d{4})\. (.+?) :(easy|medium|hard):", text, re.MULTILINE, ) if not m: return None num = m.group(1) name = m.group(2).strip() difficulty = m.group(3) # Topic = parent directory name topic_slug = path.parent.name topic = TOPIC_DISPLAY.get(topic_slug, topic_slug.replace("-", " ").title()) # Problem statement: first non-empty, non-heading, non-property line after :END: after_props = text[m.end():] lines = after_props.split("\n") statement_lines = [] in_block = False for line in lines: stripped = line.strip() if stripped == ":END:": continue if not stripped: if statement_lines: break continue if stripped.startswith("#+") or stripped.startswith("*") or stripped.startswith(":"): if statement_lines: break continue statement_lines.append(stripped) statement = " ".join(statement_lines).strip() # Clean up org markup for plain text statement = re.sub(r"[~=*/_]", "", statement) return { "num": num, "name": name, "difficulty": difficulty, "topic": topic, "statement": statement, "path": path, } def generate_cards(problems: list[dict]) -> str: """Generate org-mode flashcard content.""" parts = [] parts.append("#+TITLE: Auto-Generated Problem Cards") parts.append("#+ANKI_DECK: study_deck_02") parts.append("") for p in sorted(problems, key=lambda x: int(x["num"])): num = p["num"] name = p["name"] diff = p["difficulty"] topic = p["topic"] stmt = p["statement"] # Card: given problem name → what does it ask? title = f"LC {num}. {name} — what does it ask?" tags = f":leetcode:{diff}:{topic.replace(' ', '-').lower()}:retrieval::recognition:" parts.append(f"* {title} {tags}") parts.append(":PROPERTIES:") parts.append(":ANKI_NOTE_TYPE: Basic") parts.append(":END:") parts.append("** Front") parts.append(f"What does LeetCode {num} *{name}* ask you to do?") parts.append("** Back") parts.append(stmt) parts.append("") return "\n".join(parts) def main(): dry_run = "--dry-run" in sys.argv org_files = sorted(DSA.rglob("*.org")) org_files = [f for f in org_files if f.name != "udfs.org"] print(f"Found {len(org_files)} problem files") problems = [] skipped = 0 for f in org_files: p = parse_file(f) if p: problems.append(p) else: skipped += 1 print(f" skipped: {f.name}") print(f"Parsed: {len(problems)} problems, {skipped} skipped") content = generate_cards(problems) if dry_run: print(f"\nWould write {len(problems)} cards to {OUTPUT}") # Show first 3 cards as preview preview = content.split("\n\n\n")[:3] print("\n--- Preview ---") print("\n\n".join(preview)) else: OUTPUT.parent.mkdir(parents=True, exist_ok=True) OUTPUT.write_text(content) print(f"\nWrote {len(problems)} cards to {OUTPUT}") if __name__ == "__main__": main()