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:
@@ -67,10 +67,27 @@ class Solution:
|
||||
|
||||
** TODO C++
|
||||
#+begin_src cpp :lc-problem 1
|
||||
#include <algorithm>
|
||||
class Solution {
|
||||
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
|
||||
|
||||
#+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
|
||||
@@ -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()
|
||||
@@ -71,22 +71,32 @@ Returns a plist (:problem :lang :input :code) or signals an error."
|
||||
(display-buffer (process-buffer proc)))))
|
||||
(display-buffer buf)))
|
||||
|
||||
(defvar lc-org-mode-map
|
||||
(let ((map (make-sparse-keymap))
|
||||
(prefix (make-sparse-keymap)))
|
||||
(defvar lc-org-mode-map (make-sparse-keymap)
|
||||
"Keymap for `lc-org-mode'.")
|
||||
|
||||
(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 "r") #'lc-org-run)
|
||||
(define-key prefix (kbd "t") #'lc-org-status)
|
||||
(define-key prefix (kbd "p") #'lc-org-show-problem)
|
||||
(define-key prefix (kbd "d") #'lc-org-daily)
|
||||
(define-key map (kbd "C-c l") prefix)
|
||||
map)
|
||||
"Keymap for `lc-org-mode'.")
|
||||
(define-key lc-org-mode-map (kbd "C-c l") prefix)))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode lc-org-mode
|
||||
"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
|
||||
r run — run with test input
|
||||
t status — check submission status
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user