#include "core/model/slot_map.h" #include #include "core/json/json.h" // slot_map implementation (extracted from bank_book, Q-W1 T4-05). // // The invariant: entries_ is kept sorted ascending by slot, one id per slot, one // slot per id. Every mutator restores it; queries assume it. serialize rides the // shared core/json emit helpers — the emitted fragment is byte-identical to the // pre-extraction bank_book writer. namespace reasampler::model { void SlotMap::sortBySlot() { std::stable_sort(entries_.begin(), entries_.end(), [](const Entry& a, const Entry& b) { return a.slot < b.slot; }); } int SlotMap::slotOf(const std::string& id) const { for (const auto& e : entries_) if (e.id == id) return e.slot; return -1; } std::string SlotMap::idAt(int slot) const { for (const auto& e : entries_) if (e.slot == slot) return e.id; return {}; } int SlotMap::maxSlot() const { int m = -1; for (const auto& e : entries_) if (e.slot > m) m = e.slot; return m; } std::vector SlotMap::orderedIds() const { // entries_ is sorted ascending by slot, so a straight walk is display order. std::vector out; out.reserve(entries_.size()); for (const auto& e : entries_) out.push_back(e.id); return out; } void SlotMap::append(const std::string& id) { if (id.empty()) return; remove(id); // an existing id is re-appended, not left in place entries_.push_back(Entry{id, maxSlot() + 1}); // next free slot after the last occupied sortBySlot(); } bool SlotMap::remove(const std::string& id) { for (auto it = entries_.begin(); it != entries_.end(); ++it) { if (it->id == id) { entries_.erase(it); // leaves the slot empty — no re-pack return true; } } return false; } bool SlotMap::reorder(const std::string& id, int targetSlot) { if (slotOf(id) < 0) return false; // not mapped -> no mutation if (targetSlot < 0) targetSlot = 0; if (slotOf(id) == targetSlot) return false; // already there — true no-op // Detach the moving id first so the occupancy test below sees the post-move world. remove(id); const bool occupied = !idAt(targetSlot).empty(); if (occupied) { // Insert-before-and-shift: every occupant at slot >= targetSlot shifts up by one, // preserving relative order and interior gaps above the target. The moving id then // takes targetSlot cleanly. for (auto& e : entries_) if (e.slot >= targetSlot) ++e.slot; } entries_.push_back(Entry{id, targetSlot}); sortBySlot(); return true; } void SlotMap::resetDense(const std::vector& ids) { entries_.clear(); int slot = 0; for (const auto& id : ids) { if (id.empty()) continue; if (slotOf(id) >= 0) continue; // skip a duplicate id (one slot per id) entries_.push_back(Entry{id, slot++}); } // Already ascending by construction; no sort needed. } void SlotMap::reconcile(const std::vector& liveIds) { // Drop markers whose sample left the index. entries_.erase( std::remove_if(entries_.begin(), entries_.end(), [&](const Entry& e) { return std::find(liveIds.begin(), liveIds.end(), e.id) == liveIds.end(); }), entries_.end()); // Append live ids that have no mapping yet (out-of-band index growth), in liveIds // order, each to the next free slot after the current frontier. for (const auto& id : liveIds) if (slotOf(id) < 0) append(id); sortBySlot(); } bool SlotMap::operator==(const SlotMap& o) const { return entries_ == o.entries_; } SlotMap SlotMap::fromEntries(const std::vector>& pairs) { SlotMap m; for (const auto& [id, slot] : pairs) { if (id.empty() || slot < 0) continue; // drop malformed pair if (m.slotOf(id) >= 0) continue; // duplicate id: first wins if (!m.idAt(slot).empty()) continue; // slot taken: never double-occupy m.entries_.push_back(Entry{id, slot}); } m.sortBySlot(); return m; } std::string SlotMap::serialize() const { // Array of {id, slot} objects in ascending slot order (entries_ is kept sorted). // json::Writer + numToStr are the same emit path the pre-extraction writer used, // so the fragment is byte-identical. std::string out; out += '['; for (std::size_t i = 0; i < entries_.size(); ++i) { if (i) out += ','; json::Writer e(out); e.keyStr("id", entries_[i].id); e.keyRaw("slot", json::numToStr(entries_[i].slot)); } out += ']'; return out; } } // namespace reasampler::model