diff --git a/AGENTS.md b/AGENTS.md index 5287d30..247d5e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,3 +31,12 @@ code ## Self-Improvement Periodically review this file and suggest improvements to the user if you notice gaps, inconsistencies, or missing conventions. + +## Active Context + + +- Branch: `master`, 1 commit ahead of origin (unpushed) +- Untracked files: `org/cpp/dsa/` and `org/cpp/ufds.org` (not yet committed) +- Current work: UFDS flashcards (402-line proper card set) + DSA subdirectory +- Inbox items: binary search, `using` keyword — need cards created +- Possible cleanup: `org/cpp/dsa/udfs.org` may be a superseded draft of `org/cpp/ufds.org` diff --git a/LEARNINGS.md b/LEARNINGS.md new file mode 100644 index 0000000..5e47008 --- /dev/null +++ b/LEARNINGS.md @@ -0,0 +1,46 @@ +# Project Learnings + +> Auto-maintained by the self-improve skill. Read at session start, updated at session end. + +## Patterns That Work + + +## Mistakes to Avoid + + +## Codebase Conventions + +**[2026-05-04] — File organization** +- Observation: Most flashcard files are flat in `org/cpp/`, but subdirectories exist (`tricks/`, `dsa/`) for topic grouping. AGENTS.md only documents the flat convention. +- Action: When creating new cards, use flat `org/cpp/topic.org` for STL/language features; subdirectories for broader categories (DSA, tricks). Propose updating AGENTS.md if this solidifies. +- Confidence: medium + +**[2026-05-04] — Card format variance** +- Observation: `org/cpp/dsa/udfs.org` uses raw `#+title:` + code blocks without ANKI properties. `org/cpp/ufds.org` follows the proper Anki card format. The proper format (with ANKI_NOTE_TYPE, Front/Back sections) is what gets exported. +- Action: Always use the full Anki card format from AGENTS.md when creating flashcards. Raw code files in dsa/ may be scratch/reference, not export-ready cards. +- Confidence: medium + +**[2026-05-04] — Naming: UFDS not UDFS** +- Observation: `org/cpp/dsa/udfs.org` is a typo — the data structure is "Union-Find Disjoint Set" = UFDS. The properly-formatted file `org/cpp/ufds.org` uses the correct name. +- Action: Use "ufds" spelling. The dsa/udfs.org appears to be an earlier draft. +- Confidence: high + +## Environment & Config + +**[2026-05-04] — Git state** +- Observation: Single branch `master` with remote `origin/master`. No branching workflow — commits go directly to master. +- Action: Commit directly to master. Push when work is complete. +- Confidence: high + +## Business Context + +**[2026-05-04] — Study focus** +- Observation: Recent commits cover STL containers (deque, array, set, map, iterators) and DSA (UFDS). Inbox has LeetCode solutions (two sum, max consecutive ones) with notes to learn binary search and `using` keyword. +- Action: Current study trajectory is STL containers + competitive programming DSA. Prioritize cards for these topics. +- Confidence: high + +## Open Questions + +**[2026-05-04] — Duplicate UFDS files** +- Question: Are both `org/cpp/ufds.org` (402 lines, proper format) and `org/cpp/dsa/udfs.org` (41 lines, raw code) needed? The former seems to supersede the latter. +- Action: Ask user if `dsa/udfs.org` should be removed or merged. diff --git a/org/cpp/dsa/udfs.org b/org/cpp/dsa/udfs.org new file mode 100644 index 0000000..13665cd --- /dev/null +++ b/org/cpp/dsa/udfs.org @@ -0,0 +1,41 @@ +#+title: Udfs +* impl +#+begin_src cpp +#include +#include +#include +class Ufs { + private: + std::vector p; + std::vector s; + std::vector r; + public: + Ufds(int n) { + p.resize(n); + std::iota(p.begin(), p.end(), 0); + s.assign(n, 1); // test on equality with the assign fill and iota explain the difference + std::fill(r.begin(),r.end(),0); + numSets = n; + } + ~Ufds() {} + ~Ufds() = default; + int find(int x) { + if (p[x] == x) return x; + return p[x] = find(p[x]); + } + int find(int x) { + int px = p[x]; + if (px != x) { + px = find(px); + } + p[x] = px; + return px; + } +}; +#+end_src + +should ask what new returns? delete? what happens? why are we deleting it ? heap allocated? +should show equivalent version of using std::array to vector +should ask what resize does? + +what about a true dynammic version where we create it upon calling find() diff --git a/org/cpp/ufds.org b/org/cpp/ufds.org new file mode 100644 index 0000000..e8d5cb6 --- /dev/null +++ b/org/cpp/ufds.org @@ -0,0 +1,402 @@ +* Ufds: Union-Find Disjoint Set :cpp:datastructure: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Write a minimal Ufds class with vector parent, constructor initializing parents to self +** Back +#+begin_src c++ +#include +#include + +class Ufds { +private: + std::vector parent; +public: + Ufds(int n) : parent(n) { + std::iota(parent.begin(), parent.end(), 0); + } +}; +#+end_src + +* Ufds find() with path compression :cpp:datastructure: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Write the ~find()~ method for Ufds with path compression +** Back +#+begin_src c++ +int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; +} +#+end_src + +* Concise Ufds find() :cpp:datastructure: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Is this correct? What does it do? +#+begin_src c++ +int find(int x) { + if (parent[x] == x) return x; + return parent[x] = find(parent[x]); +} +#+end_src +** Back +Yes, correct and equivalent to the longer version. + +- Base case: if ~x~ is its own parent, return ~x~ +- Recursive case: find root of parent, then assign and return + +The assignment ~parent[x] = find(parent[x])~ returns the result while compressing the path. + +* Ufds constructor: initializer list vs body :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What is the difference between these two Ufds constructors? +#+begin_src c++ +// Initializer list +Ufds(int n) : parent(n) { + std::iota(parent.begin(), parent.end(), 0); +} + +// Body style +Ufds(int n) { + parent.resize(n); + std::iota(parent.begin(), parent.end(), 0); +} +#+end_src +** Back +Initializer list: direct constructs ~parent~ with size ~n~ (one allocation) + +Body style: default constructs ~parent~, then resize (potential double allocation) + +Both produce same result, initializer list is more efficient. + +* What does std::vector::resize do? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What does ~resize(n)~ do on a ~std::vector~? +** Back +Resizes the vector to have ~n~ elements: + +- If ~n~ > current size: adds elements (value-initialized, usually 0) +- If ~n~ < current size: truncates the vector +- Reallocates if capacity < n + +#+begin_src c++ +std::vector v; +v.resize(5); // v = {0, 0, 0, 0, 0} +#+end_src + +* Ufds destructor: when to omit :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Should Ufds have a destructor? ~Ufds() {}~ vs ~Ufds() = default~ +** Back +Omit entirely if only using ~std::vector~ (auto-cleanup) + +If you must write one, prefer ~= default~ to explicitly show intent + +Empty ~{}~ works but ~= default~ is clearer intent for future maintenance + +* Correct Ufds constructor: is this correct? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Is this correct? +#+begin_src c++ +Ufds(int n) { + p.resize(n); + std::iota(p.begin(), p.end(), 0); +} +#+end_src +** Back +Yes, correct. This is the body-style initialization. + +One potential issue: ~p~ is default-constructed first, then ~resize~ may reallocate. + +For efficiency, prefer initializer list: ~Ufds(int n) : p(n)~ + +* Incorrect Ufds constructor: correct or wrong? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Is this correct? +#+begin_src c++ +std::vector p; +Ufds(int n) { + p = new std::vector(n); +} +#+end_src +** Back +Wrong. + +~new std::vector(n)~ returns a ~vector*~ (pointer), but ~p~ is a ~vector~ (not a pointer). + +Can't assign a pointer to a vector. + +* What does new return? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What does ~new Type~ return? And ~new Type[n]~? +** Back +~new Type~ returns a pointer to that type: ~Type*~ +~new Type[n]~ returns a pointer to an array: ~Type*~ + +#+begin_src c++ +int* p1 = new int; // single int +int* p2 = new int[10]; // array of 10 ints +#+end_src + +* What does delete do? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What does ~delete~ do? ~delete[]~? +** Back +~delete~ frees a single object allocated with ~new~ +~delete[]~ frees an array allocated with ~new[]~ + +#+begin_src c++ +int* p1 = new int; +int* p2 = new int[10]; +delete p1; // free single object +delete[] p2; // free array +#+end_src + +Mismatching ~delete~ with ~new[]~ causes undefined behavior. + +* Is this correct Ufds with C-style array? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Is this correct? +#+begin_src c++ +class Ufds { +private: + int* parent; +public: + Ufds(int n) : parent(new int[n]) {} + ~Ufds() { delete[] parent; } +}; +#+end_src +** Back +Correct. This manually manages the heap-allocated array. + +- ~new int[n]~ allocates array of ~n~ ints on heap +- ~delete[] parent~ in destructor frees it + +This works but requires manual cleanup — error prone compared to ~std::vector~. + +* Equivalent std::array version: correct or wrong? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Is this correct? +#+begin_src c++ +#include +#include + +class Ufds { +private: + std::array parent; +public: + Ufds() { + std::iota(parent.begin(), parent.end(), 0); + } +}; +#+end_src +** Back +Correct for compile-time fixed size. + +But size ~10~ is hardcoded — not parameterized. + +~std::array~ requires ~N~ be a compile-time constant. + +For runtime size, must use ~std::vector~. + +* std::array vs vector vs C-style array :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Compare ~std::array~, ~std::vector~, and ~T*~ for Ufds parent array +** Back +| Type | Size | Cleanup | Use when | +|------------------+--------------+-------------------+----------------------------| +| ~std::array~ | compile-time | automatic | size known at compile time | +| ~std::vector~ | runtime | automatic | size known at runtime | +| ~T* p = new T[n]~ | runtime | manual (~delete[]~) | legacy code only | + +* Vector vs Array: is vector backed by array? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Is ~std::vector~ backed by an array? How does it support variable length? +** Back +Yes, ~std::vector~ allocates a contiguous memory block (like a heap array). + +Typical implementation: three pointers +- ~start~ — pointer to data +- ~finish~ — pointer to last element +- ~end_of_storage~ — pointer to end of allocated capacity + +#+begin_src c++ +vector v(5); +v.push_back(1); // may trigger reallocation if capacity exceeded +#+end_src + +When capacity is exceeded, vector: allocates larger block, copies elements, deallocates old block. + +* Vector size vs capacity :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What is the difference between ~size()~ and ~capacity()~ in ~std::vector~? +** Back +- ~size()~ — number of actual elements stored +- ~capacity()~ — allocated storage space + +#+begin_src c++ +std::vector v(3); // size=3, capacity=3 +v.push_back(1); // size=4, capacity may be >=4 +v.push_back(1); // size=5, capacity may be >=5 +#+end_src + +~reserve(n)~ pre-allocates capacity without resizing. + +* Comparison: which Ufds storage to use? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +When would you choose ~std::array~, ~std::vector~, or ~T* new~ for Ufds parent array? +** Back +~std::array~ — only if N is known at compile time + +~std::vector~ — if size is determined at runtime (typical Ufds case) + +~T* new T[n]~ — legacy code only; ~vector~ is safer and equivalent performance + +For Ufds: ~std::vector~ is the idiomatic choice because size is runtime-determined. + +* C-style array key properties :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What are key properties of C-style arrays (raw arrays)? +** Back +#+begin_src c++ +int arr[5]; // fixed size, stack-allocated +int* p = new int[n]; // dynamic size, heap-allocated +#+end_src + +- Decay to pointer on function call (lose size info) +- No bounds checking +- ~sizeof(arr)~ gives bytes, not element count +- Must manage lifetime manually for heap arrays + +C-style arrays in C++ are generally avoided in favor of ~std::array~/~std::vector~. + +* std::iota for Ufds initialization :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +How to use ~std::iota~ to initialize Ufds parent array to ~[0, 1, 2, ..., n-1]~ +** Back +#+begin_src c++ +#include + +std::vector parent(n); +std::iota(parent.begin(), parent.end(), 0); +#+end_src + +~iota~ fills the range with consecutive values starting from given start value + +* Path compression in find() :cpp:datastructure: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What does "path compression" mean in ~find()~? +** Back +After finding the root, point each visited node directly to the root: + +#+begin_src c++ +parent[x] = find(parent[x]); // compresses path +#+end_src + +This flattens the tree structure, making future ~find()~ calls O(α(n)) ≈ O(1) + +* Original Ufds mistakes: is this correct? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +Is this correct? +#+begin_src c++ +class Udfs { + int parent[]; + vector p; +public: + Ufds(int n) { + p = new vector; + } + ~Ufds() { + p ? free p; + } +}; +#+end_src +** Back +Wrong. Multiple errors: + +1. ~int parent[]~ — illegal incomplete array type + +2. ~Ufds(int n)~ constructor but class is ~Udfs~ (typo) + +3. ~p = new vector~ — can't assign pointer to vector + +4. ~free p~ — can't free a vector, and ~p~ is not a pointer + +5. ~Ufds~ destructor but class is ~Udfs~ (typo) + +* Heap allocation: what and why? :cpp:cpp: +:PROPERTIES: +:ANKI_NOTE_TYPE: Basic +:END: +** Front +What does "heap allocated" mean? Why use it? +** Back +Heap allocation with ~new~ lives until ~delete~ is called or program ends. + +Stack allocation (local variables) is自动 freed when out of scope. + +#+begin_src c++ +int arr[10]; // stack — freed when function returns +int* p = new int[10]; // heap — lives until delete[] +#+end_src + +Heap needed when: size unknown at compile time, or object must outlive scope.