collections
import collections from "collections";
var words: collections.Vec_string = collections.vec_string_new();
words.push("apple");
words.push("pear");
var counts: collections.Map_string_i64 = collections.map_string_i64_new();
counts.set("apple", 3);
let n: i64 = counts.get("apple")!; // 3
let missing: ?i64 = counts.get("kiwi"); // nullcollections has the containers programs need: a growable vector, a hash
map, a hash set, a double-ended queue, a binary heap and a bit set.
tape has no generics: a container type is made for one element type by a
generator, at compile time (@tape generators).
The common ones are already made, so you use them directly:
| Ready to use | Holds |
|---|---|
collections.Vec_i64, Vec_u8, Vec_f64, Vec_string | vectors |
collections.Map_string_string, Map_string_i64, Map_i64_i64 | maps |
collections.Set_string, Set_i64 | sets |
For any other type, call the generator once in your module; the types land
in your module, so you use them without the collections. prefix:
import collections from "collections";
pub struct Person { name: string; age: i64; }
@tape {
collections.gen_vec(Person); // Vec_Person, vec_Person_new()
collections.gen_map(string, Person); // Map_string_Person
}
pub fn main() -> i32 {
var people: Vec_Person = vec_Person_new();
people.push(Person { name: "Ada", age: 36 });
return 0;
}An element type you generate for must be pub. Your own Vec_i64 from
gen_vec(i64) and collections.Vec_i64 are two different types: use the
ready-made ones when they fit.
collections is a pure module: it allocates, but has no other effect.
Ownership
A container owns copies of what you put in it, and frees them:
push,set,insert,addstore a copy (a temporary, like"item-{i}"or a struct literal, is moved in rather than copied);get,front,peekand iteration give you a copy you own;popandremovegive you the element and take it out;- replacing or removing an element frees the old one;
- assigning a container copies it, elements and all; it is freed when its variable goes out of scope.
So elements can be strings, structs with strings, tagged unions, or other
containers, and nothing leaks (see Ownership).
v.free() frees a container early; using it afterwards is fine, it’s just
empty.
get_ref, get_ptr, slice and slice_mut let you work on elements in
place without copying: get_ref and slice to read them, get_ptr and
slice_mut to change them. The pointer or slice is valid until the
container next grows, shrinks or removes something.
The methods that only read (len, get, get_ref, has, iter,
slice, …) work on a container held in a let, or passed as a
parameter; the ones that change it (push, set, get_ptr, slice_mut,
clear, …) need a var:
let v: collections.Vec_i64 = load();
let n: i64 = v.len(); // fine: len only reads
v.push(1); // error: '.push()' changes 'v', a 'let' bindingIndexes run from 0 to len() - 1; one outside that stops the program with
a trap (Vec: index out of range). Absence is an optional: pop on an
empty vector and get of a missing key return null.
Vec
Vec_T is a growable array.
| Function | Does |
|---|---|
vec_T_new() -> Vec_T | An empty vector (allocates on the first push) |
vec_T_with_cap(cap: i64) -> Vec_T | An empty vector with room for cap elements |
v.len() -> i64, v.is_empty() -> bool, v.cap() -> i64 | Size and capacity |
v.push(item: T) | Appends a copy |
v.push_take(item: *T) | Appends *item without copying it; *item is left zeroed (an empty string or container) |
v.extend(items: []const T) | Appends a copy of each |
v.pop() -> ?T | Removes and returns the last element (moved out, not copied); null when empty |
v.get(i: i64) -> T | A copy of element i |
v.get_ref(i: i64) -> *const T | Element i in place, read-only |
v.get_ptr(i: i64) -> *T | Element i in place, to change it |
v.set(i: i64, item: T) | Replaces element i |
v.set_take(i: i64, item: *T) | Replaces element i with *item, not copied; *item left zeroed |
v.insert(i: i64, item: T) | Inserts before element i (i == len appends) |
v.insert_take(i: i64, item: *T) | Inserts *item before element i, not copied; *item left zeroed |
v.remove(i: i64) -> T | Removes and returns element i (moved out, not copied); the rest move down |
v.truncate(n: i64) | Keeps the first n elements |
v.reserve(n: i64) | Makes room for n more without growing again |
v.clear() | Removes everything, keeps the memory |
v.free() | Removes everything and frees the memory |
v.slice() -> []const T | The elements in place, read-only: for bytes, fs.write_file and loops |
v.slice_mut() -> []T | The elements in place, to change: for sort |
v.iter() | Iterates over copies, first to last |
push, set and insert store a copy of what you give them. When you
are done with a value (a block you built, a string you made), the _take
forms move it in instead: v.push_take(&b) stores b without copying its
strings and vectors, and leaves b zeroed (an empty value that owns
nothing). pop and remove hand you the element itself.
var v: collections.Vec_i64 = collections.vec_i64_new();
for (i in 0..5) { v.push(i * i); } // 0 1 4 9 16
v.insert(0, -1); // -1 0 1 4 9 16
let last: i64 = v.pop()!; // 16
var sum: i64 = 0;
for (x in v.iter()) { sum = sum + x; } // 13Map
Map_K_V is a hash map from keys K to values V.
| Function | Does |
|---|---|
map_K_V_new() -> Map_K_V | An empty map |
m.len() -> i64, m.is_empty() -> bool | Number of entries |
m.set(key: K, value: V) | Sets key to a copy of value, replacing an old value |
m.get(key) -> ?V | A copy of the value; null when key is absent |
m.get_ref(key) -> ?*const V | The value in place, read-only; null when absent |
m.get_ptr(key) -> ?*V | The value in place, to change it; null when absent |
m.get_ptr_or_set(key, value) -> *V | The value in place, key set to value first when absent (tell which by m.len() growing): one lookup where has, set and get_ptr take three |
m.has(key) -> bool | Whether key is there |
m.remove(key) -> bool | Removes the entry; false when absent |
m.clear(), m.free() | Removes every entry (and frees the memory) |
m.iter() | Iterates over entries, each with .key and .value |
Keys can be integers, bool, enums, pointers, string or []const u8.
Text keys are compared by content. A string-keyed map looks up with any
text: a literal, a string, or a slice of one, without allocating. A
[]const u8 key is stored as the slice, not a copy, so the text it points
to must outlive the map.
var counts: collections.Map_string_i64 = collections.map_string_i64_new();
for (w in bytes.split("a b a c a", " ")) {
let c: ?i64 = counts.get(w);
if (c == null) { counts.set("{w}", 1); } else { counts.set("{w}", c! + 1); }
}
// counts.get("a")! == 3
for (e in counts.iter()) {
io.println("{e.key}: {e.value}");
}The iteration order isn’t sorted or insertion order, but it is the same every time the same operations run, in the VM and natively.
Floats aren’t keys (NaN isn’t equal to itself). For a struct key, or any
key with your own idea of equality, use gen_map_by with a hash function
and an equality function; keys that are equal must hash the same:
pub struct Point { x: i64; y: i64; }
fn point_hash(p: *const Point) -> u64 { return hash.hash_i64(p.x * 31 + p.y); }
fn point_eq(a: *const Point, b: *const Point) -> bool { return a.x == b.x && a.y == b.y; }
@tape { collections.gen_map_by(Point, string, point_hash, point_eq); }
var names: Map_Point_string = map_Point_string_new();
names.set(Point { x: 1, y: 2 }, "home");Set
Set_T holds each value once: the keys of a map without the values.
Same key types as Map; gen_set_by for others.
| Function | Does |
|---|---|
set_T_new() -> Set_T | An empty set |
s.len() -> i64, s.is_empty() -> bool | Number of elements |
s.add(key: T) -> bool | Adds a copy; false if it was already there |
s.has(key) -> bool | Whether key is there |
s.remove(key) -> bool | Removes it; false when absent |
s.clear(), s.free() | Removes everything (and frees the memory) |
s.iter() | Iterates over copies of the elements |
Deque
Deque_T is a queue you can push to and pop from at both ends, in constant
time. Generate it with collections.gen_deque(T).
| Function | Does |
|---|---|
deque_T_new() -> Deque_T | An empty deque |
d.len() -> i64, d.is_empty() -> bool | Number of elements |
d.push_back(item: T), d.push_front(item: T) | Adds a copy at that end |
d.pop_back() -> ?T, d.pop_front() -> ?T | Removes and returns from that end; null when empty |
d.front() -> ?T, d.back() -> ?T | A copy of the element at that end |
d.get(i: i64) -> T, d.set(i: i64, item: T) | Element i, counted from the front |
d.clear(), d.free() | Removes everything (and frees the memory) |
d.iter() | Iterates over copies, front to back |
@tape { collections.gen_deque(i64); }
var q: Deque_i64 = deque_i64_new();
q.push_back(1);
q.push_back(2);
let first: i64 = q.pop_front()!; // 1Heap
Heap_T_by_less is a priority queue: pop always gives the least element
by the less function you pass to collections.gen_heap(T, less), and
the type is named after both. With “a < b”
you get the smallest first; with “a > b”, the largest.
| Function | Does |
|---|---|
heap_T_by_less_new() -> Heap_T_by_less | An empty heap |
h.len() -> i64, h.is_empty() -> bool | Number of elements |
h.push(item: T) | Adds a copy |
h.pop() -> ?T | Removes and returns the least element; null when empty |
h.peek() -> ?T | A copy of the least element |
h.clear(), h.free() | Removes everything (and frees the memory) |
pub struct Job { pri: i64; name: string; }
fn more_urgent(a: *const Job, b: *const Job) -> bool { return a.pri > b.pri; }
@tape { collections.gen_heap(Job, more_urgent); }
var jobs: Heap_Job_by_more_urgent = heap_Job_by_more_urgent_new();
jobs.push(Job { pri: 1, name: "tidy" });
jobs.push(Job { pri: 9, name: "ship" });
let next: Job = jobs.pop()!; // "ship"Elements that compare equal come out in no particular order. Each order is
its own type, so one module can have several heaps of one element type
(Heap_Job_by_more_urgent, Heap_Job_by_oldest).
BitSet
A fixed number of bits, all clear at first.
| Function | Does |
|---|---|
collections.bitset(n: i64) -> BitSet | n bits, all clear |
b.len() -> i64 | The number of bits |
b.set(i), b.clear(i), b.toggle(i) | Changes bit i |
b.test(i) -> bool | Whether bit i is set |
b.count() -> i64 | How many bits are set |
b.set_all(), b.clear_all() | Every bit |
b.free() | Frees the bits; the set is then empty |
var seen: collections.BitSet = collections.bitset(1000);
seen.set(42);
if (seen.test(42)) { /* ... */ }A BitSet is copied by assignment and freed at scope end, like the other
containers.
Generators
| Generator | Makes |
|---|---|
gen_vec(T) | Vec_T, vec_T_new, vec_T_with_cap |
gen_map(K, V) | Map_K_V, map_K_V_new (built-in key types) |
gen_map_by(K, V, hash_fn, eq_fn) | the same with your fn(*const K) -> u64 and fn(*const K, *const K) -> bool |
gen_set(T), gen_set_by(T, hash_fn, eq_fn) | Set_T, set_T_new |
gen_deque(T) | Deque_T, deque_T_new |
gen_heap(T, less) | Heap_T_by_less, heap_T_by_less_new, ordered by your fn(*const T, *const T) -> bool |
Type names follow the $T naming:
Map_cslice_u8_i64 for []const u8 keys, Vec_Vec_string for a vector of
vectors. Each made type also gets _copy and _drop functions
(Vec_Person_copy), which the compiler calls when you assign or drop one;
you don’t call them yourself.
Last modified: