// std.collections — Set, Queue, Stack as struct types.
//
// Bop's user methods pass `self` by value, and the built-in
// mutating array methods (`push`, `pop`, …) only write back
// when the receiver is a bare ident (not a field access). So
// methods here return a fresh instance and the caller rebinds:
//
// let s = stack()
// s = s.push(1)
// s = s.push(2)
//
// The pattern trades a little boilerplate for a clean value-
// semantics model: `let a = b` doesn't alias, and method calls
// never surprise you by mutating out-of-band.
// ─── Stack (LIFO) ─────────────────────────────────────────────
struct Stack {
items,
}
// Build a fresh empty stack. Preferred over
// `Stack { items: [] }` for symmetry with `queue()` / `set()`.
fn stack() {
return Stack { items: [] }
}
fn Stack.is_empty(self) {
return self.items.len() == 0
}
fn Stack.size(self) {
return self.items.len()
}
// Return a new stack with `v` pushed onto the top.
fn Stack.push(self, v) {
self.items = self.items + [v]
return self
}
// Peek at the top value, or `none` when empty.
fn Stack.top(self) {
if self.is_empty() { return none }
return self.items[self.items.len() - 1]
}
// Return a new stack with the top element removed. Popping an
// empty stack returns it unchanged — callers who care should
// check `is_empty` or `top` first.
fn Stack.pop(self) {
if self.is_empty() { return self }
self.items = self.items.slice(0, self.items.len() - 1)
return self
}
// ─── Queue (FIFO) ─────────────────────────────────────────────
struct Queue {
items,
}
fn queue() {
return Queue { items: [] }
}
fn Queue.is_empty(self) {
return self.items.len() == 0
}
fn Queue.size(self) {
return self.items.len()
}
// Return a new queue with `v` appended at the back.
fn Queue.enqueue(self, v) {
self.items = self.items + [v]
return self
}
// Peek at the front element, or `none` when empty.
fn Queue.front(self) {
if self.is_empty() { return none }
return self.items[0]
}
// Return a new queue with the front element removed.
// Dequeue is O(n) in this naive implementation — good enough
// for scripting workloads; an array-backed ring buffer would
// pay for itself at larger scales.
fn Queue.dequeue(self) {
if self.is_empty() { return self }
self.items = self.items.slice(1, self.items.len())
return self
}
// ─── Set (unique elements, insertion-ordered) ────────────────
struct Set {
items,
}
fn set() {
return Set { items: [] }
}
// Build a set pre-populated with the elements of `arr`.
// Duplicates collapse (first-seen wins ordering).
fn set_of(arr) {
let s = set()
for v in arr { s = s.add(v) }
return s
}
fn Set.is_empty(self) {
return self.items.len() == 0
}
fn Set.size(self) {
return self.items.len()
}
// `true` when `v` is in the set.
fn Set.has(self, v) {
return self.items.has(v)
}
// Return a new set with `v` added. Idempotent: adding an
// already-present value is a no-op.
fn Set.add(self, v) {
if self.items.has(v) { return self }
self.items = self.items + [v]
return self
}
// Return a new set with `v` removed. Removing an absent value
// is a no-op. Order of remaining elements is preserved.
fn Set.remove(self, v) {
let idx = self.items.index_of(v)
if idx < 0 { return self }
let before = self.items.slice(0, idx)
let after = self.items.slice(idx + 1, self.items.len())
self.items = before + after
return self
}
// Return the set's elements as an array (insertion order).
fn Set.values(self) {
return self.items
}
// Return a new set that's the union of `self` and `other`.
fn Set.union(self, other) {
let result = self
for v in other.items {
result = result.add(v)
}
return result
}
// Return a new set that's the intersection of `self` and
// `other` — elements present in both.
fn Set.intersect(self, other) {
let result = set()
for v in self.items {
if other.has(v) {
result = result.add(v)
}
}
return result
}
// Return a new set that's `self` minus `other` — elements of
// `self` that aren't in `other`.
fn Set.difference(self, other) {
let result = set()
for v in self.items {
if !other.has(v) {
result = result.add(v)
}
}
return result
}