add flashcard generation tooling and binary search cards

- gen-flashcards.py: auto-generate recognition cards from all problem files
- toolkit/gen-problem-cards.org: 199 auto-generated problem cards
- 5 binary search tool cards (std::binary_search, std::lower_bound, comparison, two-sum pattern, sorting gotcha)
- two-sum.org: add binary search C++ attempt
- lc-org.el: add doom emacs localleader keybinding support
This commit is contained in:
2026-06-08 01:28:25 +08:00
parent c67841fe07
commit e10cc4257d
9 changed files with 2052 additions and 9 deletions
@@ -67,10 +67,27 @@ class Solution:
** TODO C++ ** TODO C++
#+begin_src cpp :lc-problem 1 #+begin_src cpp :lc-problem 1
#include <algorithm>
class Solution { class Solution {
public: public:
vector<int> twoSum(vector<int>& nums, int target) { vector<int> twoSum(vector<int>& nums, int target) {
std::sort(nums.begin(), nums.end());
for (int i=0; i< nums.size(); i++) {
int x = nums[i];
if (x > target) {
return {};
}
// TODO: c++ binary search, i forgot how to do this
int want = target - x
auto it = std::binary_learch(nums.begin() + i + 1, nums.end(), want);
if (it != nums.end() && *it == want) {
int wi = std::distance(nums.begin(), it)
return {i, wi}
}
} }
reuturn {};
}
}; };
#+end_src #+end_src
#+RESULTS:
@@ -0,0 +1,11 @@
#+ANKI_DECK: study_deck_02
* When to use lower_bound vs binary_search :cpp:binary-search:algorithm:retrieval::recognition:
:PROPERTIES:
:END:
** Front
When should you use ~std::lower_bound~ instead of ~std::binary_search~?
** Back
Use ~lower_bound~ when you need the *position/index* of the element.
Use ~binary_search~ when you only need to know *if it exists* (bool).
@@ -0,0 +1,10 @@
#+ANKI_DECK: study_deck_02
* Two Sum gotcha: sorting destroys original indices :cpp:two-sum:binary-search:retrieval::recognition:
:PROPERTIES:
:END:
** Front
If you sort ~nums~ in-place for Two Sum, what goes wrong?
** Back
Sorting changes the indices. If the problem requires returning *original* indices, store ~{value, original_index}~ pairs before sorting.
@@ -0,0 +1,14 @@
#+ANKI_DECK: study_deck_02
* std::binary_search: check element exists :cpp:binary-search:algorithm:retrieval::recognition:
:PROPERTIES:
:END:
** Front
What does ~std::binary_search~ return, and what must the range be?
** Back
Returns ~bool~ (true if element found). The range must be *sorted*.
#+begin_src cpp
bool found = std::binary_search(vec.begin(), vec.end(), value);
#+end_src
@@ -0,0 +1,14 @@
#+ANKI_DECK: study_deck_02
* std::lower_bound: find position of element :cpp:binary-search:algorithm:retrieval::recognition:
:PROPERTIES:
:END:
** Front
What does ~std::lower_bound~ return?
** Back
An iterator to the first element *>=* the given value. Returns ~end()~ if no such element exists.
#+begin_src cpp
auto it = std::lower_bound(vec.begin(), vec.end(), value);
#+end_src
@@ -0,0 +1,16 @@
#+ANKI_DECK: study_deck_02
* Task: Two Sum complement lookup with lower_bound :cpp:binary-search:two-sum:retrieval::production:
:PROPERTIES:
:END:
** Front
Given sorted ~nums~ and index ~i~, write C++ to check if ~target - nums[i]~ exists in the rest of the array using ~std::lower_bound~.
** Back
#+begin_src cpp
int complement = target - nums[i];
auto it = std::lower_bound(nums.begin() + i + 1, nums.end(), complement);
if (it != nums.end() && *it == complement) {
return {(int)i, (int)std::distance(nums.begin(), it)};
}
#+end_src
+158
View File
@@ -0,0 +1,158 @@
#!/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()
+17 -7
View File
@@ -71,22 +71,32 @@ Returns a plist (:problem :lang :input :code) or signals an error."
(display-buffer (process-buffer proc))))) (display-buffer (process-buffer proc)))))
(display-buffer buf))) (display-buffer buf)))
(defvar lc-org-mode-map (defvar lc-org-mode-map (make-sparse-keymap)
(let ((map (make-sparse-keymap)) "Keymap for `lc-org-mode'.")
(prefix (make-sparse-keymap)))
(when (and (boundp 'doom-version) (fboundp 'map!))
(map! :map lc-org-mode-map
:localleader
:prefix "l"
"s" #'lc-org-submit
"r" #'lc-org-run
"t" #'lc-org-status
"p" #'lc-org-show-problem
"d" #'lc-org-daily))
(unless (and (boundp 'doom-version) (fboundp 'map!))
(let ((prefix (make-sparse-keymap)))
(define-key prefix (kbd "s") #'lc-org-submit) (define-key prefix (kbd "s") #'lc-org-submit)
(define-key prefix (kbd "r") #'lc-org-run) (define-key prefix (kbd "r") #'lc-org-run)
(define-key prefix (kbd "t") #'lc-org-status) (define-key prefix (kbd "t") #'lc-org-status)
(define-key prefix (kbd "p") #'lc-org-show-problem) (define-key prefix (kbd "p") #'lc-org-show-problem)
(define-key prefix (kbd "d") #'lc-org-daily) (define-key prefix (kbd "d") #'lc-org-daily)
(define-key map (kbd "C-c l") prefix) (define-key lc-org-mode-map (kbd "C-c l") prefix)))
map)
"Keymap for `lc-org-mode'.")
;;;###autoload ;;;###autoload
(define-minor-mode lc-org-mode (define-minor-mode lc-org-mode
"Minor mode for LeetCode CLI integration in org source blocks. "Minor mode for LeetCode CLI integration in org source blocks.
Keybindings under C-c l: Keybindings under localleader l (SPC m l or , l):
s submit — submit current block s submit — submit current block
r run — run with test input r run — run with test input
t status — check submission status t status — check submission status
File diff suppressed because it is too large Load Diff