0x0::big_vector
BigVector is an arbitrary sized vector-like data structure, implemented using an on-chain B+ Tree to support almost constant time (log base max_fan_out) random access, insertion and removal.
Iteration is supported by exposing access to leaf nodes (slices). Finding the initial slice can be done in almost constant time, and subsequently finding the previous or next slice can also be done in constant time.
Nodes in the B+ Tree are stored as individual dynamic fields
hanging off the BigVector
.
Note: The index type is u128
, but the length is stored as u64
because the expectation is that indices are sparsely distributed.
BigVector
Slice
SliceRef
empty
destroy_empty
is_empty
length
depth
borrow
borrow_mut
insert
remove
remove_batch
slice_around
slice_following
slice_before
min_slice
max_slice
next_slice
prev_slice
borrow_slice
borrow_slice_mut
slice_is_null
slice_is_leaf
slice_next
slice_prev
slice_length
slice_key
slice_borrow
slice_borrow_mut
alloc
singleton
branch
drop_slice
find_leaf
find_min_leaf
find_max_leaf
slice_bisect_left
slice_bisect_right
slice_insert
leaf_insert
node_insert
slice_remove
leaf_remove
node_remove
slice_redistribute
slice_merge
use 0x0::utils;
use 0x1::vector;
use 0x2::dynamic_field;
use 0x2::object;
use 0x2::tx_context;
BigVector
struct BigVector<E: store> has store, key
id: object::UID
depth: u8
length: u64
max_slice_size: u64
E
).
max_fan_out: u64
root_id: u64
NO_SLICE
means
there's no root.
last_id: u64
Slice
A node in the B+ tree.
If representing a leaf node, there are as many keys as values
(such that keys[i]
is the key corresponding to vals[i]
).
A Slice<u64>
can also represent an interior node, in which
case vals
contain the IDs of its children and keys
represent the partitions between children. There will be one
fewer key than value in this configuration.
struct Slice<E: store> has drop, store
SliceRef
Wrapper type around indices for slices. The internal index is the ID of the dynamic field containing the slice.
struct SliceRef has copy, drop, store
ix: u64
Tried to redistribute between two nodes, but the operation would have had no effect.
const EBadRedistribution: u64 = 9;
Found a node in an unexpected state during removal (namely, we tried to remove from a node’s child and found that it had become empty, which should not be possible).
const EBadRemove: u64 = 7;
Key already exists in BigVector
.
const EExists: u64 = 6;
Max Fan-out provided is too big.
const EFanOutTooBig: u64 = 3;
Max Fan-out provided is too small.
const EFanOutTooSmall: u64 = 2;
Found a pair of nodes that are expected to be adjacent but whose linked list pointers don’t match up.
const ENotAdjacent: u64 = 8;
BigVector
is not empty.
const ENotEmpty: u64 = 4;
Key not found in BigVector
.
const ENotFound: u64 = 5;
Max Slice Size provided is too big.
const ESliceTooBig: u64 = 1;
Max Slice Size provided is too small.
const ESliceTooSmall: u64 = 0;
Internal nodes of BigVector
can’t have more children than
this, to avoid hitting object size limits.
const MAX_FAN_OUT: u64 = 4096;
Leaf nodes of BigVector
can’t be bigger than this, to avoid
hitting object size limits.
const MAX_SLICE_SIZE: u64 = 262144;
We will accommodate at least this much fan out before splitting interior nodes, so that after the split, we don’t get an interior node that contains only one child.
const MIN_FAN_OUT: u64 = 4;
Leaf nodes of BigVector
should have at least 2 elements for maximum size
const MIN_SLICE_SIZE: u64 = 2;
Sentinel representing the absence of a slice.
const NO_SLICE: u64 = 0;
0b001: Node is completely empty (applies only to root).
const RM_FIX_EMPTY: u8 = 1;
0b100: Merged with the left neighbour.
const RM_FIX_MERGE_L: u8 = 4;
0b101: Merged with the right neighbour.
const RM_FIX_MERGE_R: u8 = 5;
0b000: No fix-up.
const RM_FIX_NOTHING: u8 = 0;
0b010: Stole a key from the left neighbour, additional value is the new pivot after the steal.
const RM_FIX_STEAL_L: u8 = 2;
0b011: Stole a key from the right neighbour, additional value is the new pivot after the steal.
const RM_FIX_STEAL_R: u8 = 3;
empty
Construct a new, empty BigVector
. max_slice_size
contains
the maximum size of its leaf nodes, and max_fan_out
contains
the maximum fan-out of its interior nodes.
public(friend) fun empty<E: store>(max_slice_size: u64, max_fan_out: u64, ctx: &mut tx_context::TxContext): big_vector::BigVector<E>
public(package) fun empty<E: store>(
max_slice_size: u64,
max_fan_out: u64,
ctx: &mut TxContext,
): BigVector<E> {
assert!(MIN_SLICE_SIZE <= max_slice_size, ESliceTooSmall);
assert!(max_slice_size <= MAX_SLICE_SIZE, ESliceTooBig);
assert!(MIN_FAN_OUT <= max_fan_out, EFanOutTooSmall);
assert!(max_fan_out <= MAX_FAN_OUT, EFanOutTooBig);
BigVector {
id: ctx.new(),
depth: 0,
length: 0,
max_slice_size,
max_fan_out,
root_id: NO_SLICE,
last_id: NO_SLICE,
}
}
destroy_empty
Destroy self
as long as it is empty, even if its elements
are not droppable. Fails if self
is not empty.
public(friend) fun destroy_empty<E: store>(self: big_vector::BigVector<E>)
public(package) fun destroy_empty<E: store>(self: BigVector<E>) {
let BigVector {
id,
depth: _,
length,
max_slice_size: _,
max_fan_out: _,
root_id: _,
last_id: _,
} = self;
assert!(length == 0, ENotEmpty);
id.delete();
}
is_empty
Whether self
contains no elements or not.
public(friend) fun is_empty<E: store>(self: &big_vector::BigVector<E>): bool
public(package) fun is_empty<E: store>(self: &BigVector<E>): bool {
self.length == 0
}
length
The number of elements contained in self
.
public(friend) fun length<E: store>(self: &big_vector::BigVector<E>): u64
depth
The number of nodes between the root and the leaves in self
.
This is within a constant factor of log base max_fan_out
of
the length.
public(friend) fun depth<E: store>(self: &big_vector::BigVector<E>): u8
borrow
Access the element at index ix
in self
.
public(friend) fun borrow<E: store>(self: &big_vector::BigVector<E>, ix: u128): &E
public(package) fun borrow<E: store>(self: &BigVector<E>, ix: u128): &E {
let (ref, offset) = self.slice_around(ix);
let slice = self.borrow_slice(ref);
slice_borrow(slice, offset)
}
borrow_mut
Access the element at index ix
in self
, mutably.
public(friend) fun borrow_mut<E: store>(self: &mut big_vector::BigVector<E>, ix: u128): &mut E
public(package) fun borrow_mut<E: store>(
self: &mut BigVector<E>,
ix: u128,
): &mut E {
let (ref, offset) = self.slice_around(ix);
let slice = self.borrow_slice_mut(ref);
slice_borrow_mut(slice, offset)
}
insert
Add val
to self
at index key
. Aborts if key
is already
present in self
.
public(friend) fun insert<E: store>(self: &mut big_vector::BigVector<E>, key: u128, val: E)
public(package) fun insert<E: store>(
self: &mut BigVector<E>,
key: u128,
val: E,
) {
self.length = self.length + 1;
if (self.root_id == NO_SLICE) {
self.root_id = self.alloc(singleton(key, val));
return
};
let (root_id, depth) = (self.root_id, self.depth);
let (key, other) = self.slice_insert(root_id, depth, key, val);
if (other != NO_SLICE) {
self.root_id = self.alloc(branch(key, root_id, other));
self.depth = self.depth + 1;
}
}
remove
Remove the element with key key
from self
, returning its
value. Aborts if key
is not found.
public(friend) fun remove<E: store>(self: &mut big_vector::BigVector<E>, key: u128): E
public(package) fun remove<E: store>(self: &mut BigVector<E>, key: u128): E {
if (self.root_id == NO_SLICE) {
abort ENotFound
};
self.length = self.length - 1;
let (root_id, depth) = (self.root_id, self.depth);
let (val, rm_fix, _) = self.slice_remove(
NO_SLICE,
0u128,
root_id,
0u128,
NO_SLICE,
depth,
key,
);
if (rm_fix == RM_FIX_EMPTY) {
if (self.depth == 0) {
let Slice<E> {
prev: _,
next: _,
keys: _,
vals,
} = df::remove(&mut self.id, root_id);
// SAFETY: The slice is guaranteed to be empty because
// it is a leaf and we received the RM_FIX_EMPTY
// fix-up.
vals.destroy_empty();
self.root_id = NO_SLICE;
} else {
let mut root: Slice<u64> = df::remove(&mut self.id, root_id);
self.root_id = root.vals.pop_back();
self.depth = self.depth - 1;
}
};
val
}
remove_batch
Remove elements from self
at the indices in keys
,
returning the associated values.
Aborts if any of the keys are not found.
public(friend) fun remove_batch<E: store>(_self: &mut big_vector::BigVector<E>, _keys: vector<u128>): vector<E>
public(package) fun remove_batch<E: store>(
_self: &mut BigVector<E>,
_keys: vector<u128>,
): vector<E> {
abort 0
}
slice_around
Find the slice that contains the key-value pair for key
,
assuming it exists in the data structure. Returns the
reference to the slice and the local offset within the slice
if it exists, aborts with ENotFound
otherwise.
public(friend) fun slice_around<E: store>(self: &big_vector::BigVector<E>, key: u128): (big_vector::SliceRef, u64)
public(package) fun slice_around<E: store>(
self: &BigVector<E>,
key: u128,
): (SliceRef, u64) {
if (self.root_id == NO_SLICE) {
abort ENotFound
};
let (ix, leaf, off) = self.find_leaf(key);
if (off >= leaf.keys.length()) {
abort ENotFound
} else if (key != leaf.keys[off]) {
abort ENotFound
};
(SliceRef { ix }, off)
}
slice_following
Find the slice that contains the key-value pair corresponding
to the next key in self
at or after key
. Returns the
reference to the slice and the local offset within the slice
if it exists, or (NO_SLICE, 0), if there is no matching
key-value pair.
public(friend) fun slice_following<E: store>(self: &big_vector::BigVector<E>, key: u128): (big_vector::SliceRef, u64)
public(package) fun slice_following<E: store>(
self: &BigVector<E>,
key: u128,
): (SliceRef, u64) {
if (self.root_id == NO_SLICE) {
return (SliceRef { ix: NO_SLICE }, 0)
};
let (ix, leaf, off) = self.find_leaf(key);
if (off >= leaf.keys.length()) {
(leaf.next(), 0)
} else {
(SliceRef { ix }, off)
}
}
slice_before
Find the slice that contains the key-value pair corresponding
to the previous key in self
. Returns the reference to the slice
and the local offset within the slice if it exists, or (NO_SLICE, 0),
if there is no matching key-value pair.
public(friend) fun slice_before<E: store>(self: &big_vector::BigVector<E>, key: u128): (big_vector::SliceRef, u64)
public(package) fun slice_before<E: store>(
self: &BigVector<E>,
key: u128,
): (SliceRef, u64) {
if (self.root_id == NO_SLICE) {
return (SliceRef { ix: NO_SLICE }, 0)
};
let (ix, leaf, off) = self.find_leaf(key);
if (off == 0) {
let prev_ref = leaf.prev();
if (prev_ref.is_null()) {
(SliceRef { ix: NO_SLICE }, 0)
} else {
let prev_slice = self.borrow_slice(prev_ref);
(prev_ref, prev_slice.keys.length() - 1)
}
} else {
(SliceRef { ix }, off - 1)
}
}
min_slice
Find the slice that contains the key-value pair corresponding
to the minimum key in self
. Returns the reference to the
slice and the local offset within the slice if it exists, or
(NO_SLICE, 0), if there is no matching key-value pair.
public(friend) fun min_slice<E: store>(self: &big_vector::BigVector<E>): (big_vector::SliceRef, u64)
public(package) fun min_slice<E: store>(self: &BigVector<E>): (SliceRef, u64) {
if (self.root_id == NO_SLICE) {
return (SliceRef { ix: NO_SLICE }, 0)
};
let (ix, _, off) = self.find_min_leaf();
(SliceRef { ix }, off)
}
max_slice
Find the slice that contains the key-value pair corresponding
to the maximum key in self
. Returns the reference to the
slice and the local offset within the slice if it exists, or
(NO_SLICE, 0), if there is no matching key-value pair.
public(friend) fun max_slice<E: store>(self: &big_vector::BigVector<E>): (big_vector::SliceRef, u64)
public(package) fun max_slice<E: store>(self: &BigVector<E>): (SliceRef, u64) {
if (self.root_id == NO_SLICE) {
return (SliceRef { ix: NO_SLICE }, 0)
};
let (ix, _, off) = self.find_max_leaf();
(SliceRef { ix }, off)
}
next_slice
Given the current slice and offset, get the next slice and offset. Can be null.
public(friend) fun next_slice<E: store>(self: &big_vector::BigVector<E>, ref: big_vector::SliceRef, offset: u64): (big_vector::SliceRef, u64)
public(package) fun next_slice<E: store>(
self: &BigVector<E>,
ref: SliceRef,
offset: u64,
): (SliceRef, u64) {
let slice = self.borrow_slice(ref);
if (offset + 1 < slice.vals.length()) {
(ref, offset + 1)
} else {
(slice.next(), 0)
}
}
prev_slice
Given the current slice and offset, get the previous slice and offset. Can be null.
public(friend) fun prev_slice<E: store>(self: &big_vector::BigVector<E>, ref: big_vector::SliceRef, offset: u64): (big_vector::SliceRef, u64)
public(package) fun prev_slice<E: store>(
self: &BigVector<E>,
ref: SliceRef,
offset: u64,
): (SliceRef, u64) {
let slice = self.borrow_slice(ref);
if (offset > 0) {
(ref, offset - 1)
} else {
let mut prev_index = 0;
let prev_slice = slice.prev();
if (!prev_slice.is_null()) {
prev_index = self.borrow_slice(prev_slice).vals.length() - 1
};
(prev_slice, prev_index)
}
}
borrow_slice
Borrow a slice from this vector.
public(friend) fun borrow_slice<E: store>(self: &big_vector::BigVector<E>, ref: big_vector::SliceRef): &big_vector::Slice<E>
public(package) fun borrow_slice<E: store>(
self: &BigVector<E>,
ref: SliceRef,
): &Slice<E> {
df::borrow(&self.id, ref.ix)
}
borrow_slice_mut
Borrow a slice from this vector, mutably.
public(friend) fun borrow_slice_mut<E: store>(self: &mut big_vector::BigVector<E>, ref: big_vector::SliceRef): &mut big_vector::Slice<E>
public(package) fun borrow_slice_mut<E: store>(
self: &mut BigVector<E>,
ref: SliceRef,
): &mut Slice<E> {
df::borrow_mut(&mut self.id, ref.ix)
}
slice_is_null
Returns whether the SliceRef points to an actual slice, or the
NO_SLICE
sentinel. It is an error to attempt to borrow a
slice from a BigVector
if it doesn’t exist.
public(friend) fun slice_is_null(self: &big_vector::SliceRef): bool
public(package) fun slice_is_null(self: &SliceRef): bool {
self.ix == NO_SLICE
}
slice_is_leaf
Returns whether the slice is a leaf node or not. Leaf nodes have as many keys as values.
public(friend) fun slice_is_leaf<E: store>(self: &big_vector::Slice<E>): bool
public(package) fun slice_is_leaf<E: store>(self: &Slice<E>): bool {
self.vals.length() == self.keys.length()
}
slice_next
Reference to the next (neighbouring) slice to this one.
public(friend) fun slice_next<E: store>(self: &big_vector::Slice<E>): big_vector::SliceRef
public(package) fun slice_next<E: store>(self: &Slice<E>): SliceRef {
SliceRef { ix: self.next }
}
slice_prev
Reference to the previous (neighbouring) slice to this one.
public(friend) fun slice_prev<E: store>(self: &big_vector::Slice<E>): big_vector::SliceRef
public(package) fun slice_prev<E: store>(self: &Slice<E>): SliceRef {
SliceRef { ix: self.prev }
}
slice_length
Number of children (values) in this slice.
public(friend) fun slice_length<E: store>(self: &big_vector::Slice<E>): u64
public(package) fun slice_length<E: store>(self: &Slice<E>): u64 {
self.vals.length()
}
slice_key
Access a key from this slice, referenced by its offset, local to the slice.
public(friend) fun slice_key<E: store>(self: &big_vector::Slice<E>, ix: u64): u128
public(package) fun slice_key<E: store>(self: &Slice<E>, ix: u64): u128 {
self.keys[ix]
}
slice_borrow
Access a value from this slice, referenced by its offset, local to the slice.
public(friend) fun slice_borrow<E: store>(self: &big_vector::Slice<E>, ix: u64): &E
public(package) fun slice_borrow<E: store>(self: &Slice<E>, ix: u64): &E {
&self.vals[ix]
}
slice_borrow_mut
Access a value from this slice, mutably, referenced by its offset, local to the slice.
public(friend) fun slice_borrow_mut<E: store>(self: &mut big_vector::Slice<E>, ix: u64): &mut E
public(package) fun slice_borrow_mut<E: store>(
self: &mut Slice<E>,
ix: u64,
): &mut E {
&mut self.vals[ix]
}
alloc
Store slice
as a dynamic field on self
, and use its
dynamic field ID to connect it into the doubly linked list
structure at its level. Returns the ID of the slice to be used
in a SliceRef
.
fun alloc<E: store, F: store>(self: &mut big_vector::BigVector<E>, slice: big_vector::Slice<F>): u64
fun alloc<E: store, F: store>(self: &mut BigVector<E>, slice: Slice<F>): u64 {
let prev = slice.prev;
let next = slice.next;
self.last_id = self.last_id + 1;
df::add(&mut self.id, self.last_id, slice);
let curr = self.last_id;
if (prev != NO_SLICE) {
let prev: &mut Slice<F> = df::borrow_mut(&mut self.id, prev);
prev.next = curr;
};
if (next != NO_SLICE) {
let next: &mut Slice<F> = df::borrow_mut(&mut self.id, next);
next.prev = curr;
};
curr
}
singleton
Create a slice representing a leaf node containing a single key-value pair.
fun singleton<E: store>(key: u128, val: E): big_vector::Slice<E>
fun singleton<E: store>(key: u128, val: E): Slice<E> {
Slice {
prev: NO_SLICE,
next: NO_SLICE,
keys: vector[key],
vals: vector[val],
}
}
branch
Create a slice representing an interior node containing a single branch.
fun branch(key: u128, left: u64, right: u64): big_vector::Slice<u64>
fun branch(key: u128, left: u64, right: u64): Slice<u64> {
Slice {
prev: NO_SLICE,
next: NO_SLICE,
keys: vector[key],
vals: vector[left, right],
}
}
drop_slice
Recursively drop
the nodes under the node at id node
.
Assumes that node has depth depth
and is owned by id
.
fun drop_slice<E: drop, store>(id: &mut object::UID, depth: u8, slice: u64)
fun drop_slice<E: store + drop>(id: &mut UID, depth: u8, slice: u64) {
if (slice == NO_SLICE) {
return
} else if (depth == 0) {
let _: Slice<E> = df::remove(id, slice);
} else {
let mut slice: Slice<u64> = df::remove(id, slice);
while (!slice.vals.is_empty()) {
drop_slice<E>(id, depth - 1, slice.vals.pop_back());
}
}
}
find_leaf
Find the leaf slice that would contain key
if it existed in
self
. Returns the slice ref for the leaf, a reference to the
leaf, and the offset in the leaf of the key (if the key were
to exist in self
it would appear here).
Assumes self
is non-empty.
fun find_leaf<E: store>(self: &big_vector::BigVector<E>, key: u128): (u64, &big_vector::Slice<E>, u64)
fun find_leaf<E: store>(self: &BigVector<E>, key: u128): (u64, &Slice<E>, u64) {
let (mut slice_id, mut depth) = (self.root_id, self.depth);
while (depth > 0) {
let node: &Slice<u64> = df::borrow(&self.id, slice_id);
let off = node.bisect_right(key);
slice_id = node.vals[off];
depth = depth - 1;
};
let leaf: &Slice<E> = df::borrow(&self.id, slice_id);
let off = leaf.bisect_left(key);
(slice_id, leaf, off)
}
find_min_leaf
Find the minimum leaf node that contains the smallest key in the BigVector.
Assumes self
is non-empty.
fun find_min_leaf<E: store>(self: &big_vector::BigVector<E>): (u64, &big_vector::Slice<E>, u64)
fun find_min_leaf<E: store>(self: &BigVector<E>): (u64, &Slice<E>, u64) {
let (mut slice_id, mut depth) = (self.root_id, self.depth);
// Traverse down to the leftmost leaf node
while (depth > 0) {
let slice: &Slice<u64> = df::borrow(&self.id, slice_id);
slice_id = slice.vals[0]; // Always take the leftmost child
depth = depth - 1;
};
let leaf: &Slice<E> = df::borrow(&self.id, slice_id);
(slice_id, leaf, 0)
}
find_max_leaf
Find the maximum leaf node that contains the largest key in the BigVector.
Assumes self
is non-empty.
fun find_max_leaf<E: store>(self: &big_vector::BigVector<E>): (u64, &big_vector::Slice<E>, u64)
fun find_max_leaf<E: store>(self: &BigVector<E>): (u64, &Slice<E>, u64) {
let (mut slice_id, mut depth) = (self.root_id, self.depth);
// Traverse down to the rightmost leaf node
while (depth > 0) {
let slice: &Slice<u64> = df::borrow(&self.id, slice_id);
slice_id =
slice.vals[slice.keys.length()]; // Always take the rightmost child
depth = depth - 1;
};
let leaf: &Slice<E> = df::borrow(&self.id, slice_id);
(slice_id, leaf, leaf.keys.length() - 1)
}
slice_bisect_left
Find the position in slice.keys
of key
if it exists, or
the minimal position it should be inserted in to maintain
sorted order.
fun slice_bisect_left<E: store>(self: &big_vector::Slice<E>, key: u128): u64
fun slice_bisect_left<E: store>(self: &Slice<E>, key: u128): u64 {
let (mut lo, mut hi) = (0, self.keys.length());
// Invariant: keys[0, lo) < key <= keys[hi, ..)
while (lo < hi) {
let mid = (hi - lo) / 2 + lo;
if (key <= self.keys[mid]) {
hi = mid;
} else {
lo = mid + 1;
}
};
lo
}
slice_bisect_right
Find the largest index in slice.keys
to insert key
to
maintain sorted order.
fun slice_bisect_right<E: store>(self: &big_vector::Slice<E>, key: u128): u64
fun slice_bisect_right<E: store>(self: &Slice<E>, key: u128): u64 {
let (mut lo, mut hi) = (0, self.keys.length());
// Invariant: keys[0, lo) <= key < keys[hi, ..)
while (lo < hi) {
let mid = (hi - lo) / 2 + lo;
if (key < self.keys[mid]) {
hi = mid;
} else {
lo = mid + 1;
}
};
lo
}
slice_insert
Insert key: val
into the slice at ID slice_id
with depth
depth
.
Returns (0, NO_SLICE), if the insertion could be completed
without splitting, otherwise returns the key that was split
upon, and the ID of the new Slice which always sits next to
(and not previously to) slice_id
.
Upon returning, sibling pointers are fixed up, but children pointers will not be.
Aborts if key
is already found within the slice.
fun slice_insert<E: store>(self: &mut big_vector::BigVector<E>, slice_id: u64, depth: u8, key: u128, val: E): (u128, u64)
fun slice_insert<E: store>(
self: &mut BigVector<E>,
slice_id: u64,
depth: u8,
key: u128,
val: E,
): (u128, u64) {
if (depth == 0) {
self.leaf_insert(slice_id, key, val)
} else {
self.node_insert(slice_id, depth - 1, key, val)
}
}
leaf_insert
Like slice_insert
but you know that slice_id
points to a leaf node.
fun leaf_insert<E: store>(self: &mut big_vector::BigVector<E>, slice_id: u64, key: u128, val: E): (u128, u64)
fun leaf_insert<E: store>(
self: &mut BigVector<E>,
slice_id: u64,
key: u128,
val: E,
): (u128, u64) {
let leaf: &mut Slice<E> = df::borrow_mut(&mut self.id, slice_id);
let off = leaf.bisect_left(key);
if (
off < leaf.keys.length() &&
key == leaf.keys[off]
) {
abort EExists
};
// If there is enough space in the current leaf, no need
// to split.
if (leaf.keys.length() < self.max_slice_size) {
leaf.keys.insert(key, off);
leaf.vals.insert(val, off);
return (0, NO_SLICE)
};
// Split off half the current leaf to be the new `next` leaf.
let split_at = leaf.vals.length() / 2;
let mut next = Slice {
prev: slice_id,
next: leaf.next,
keys: leaf.keys.pop_until(split_at),
vals: leaf.vals.pop_until(split_at),
};
// Insert the key-value pair into the correct side of the
// split -- the first element in the new slice is the pivot.
//
// SAFETY: The next slice is guaranteed to be non-empty,
// because we round down the size of the original slice when
// splitting, so as long as `leaf.keys` had at least one
// element at the start of the call, then `next.keys` will
// have at least one element at this point.
let pivot = next.keys[0];
if (key < pivot) {
leaf.keys.insert(key, off);
leaf.vals.insert(val, off);
} else {
next.keys.insert(key, off - split_at);
next.vals.insert(val, off - split_at);
};
(pivot, self.alloc(next))
}
node_insert
Like slice_insert
but you know that slice_id
points to an
interior node, and depth
is the depth of its children, not
itself.
fun node_insert<E: store>(self: &mut big_vector::BigVector<E>, slice_id: u64, depth: u8, key: u128, val: E): (u128, u64)
fun node_insert<E: store>(
self: &mut BigVector<E>,
slice_id: u64,
depth: u8,
key: u128,
val: E,
): (u128, u64) {
let node: &mut Slice<u64> = df::borrow_mut(&mut self.id, slice_id);
let off = node.bisect_right(key);
let child = node.vals[off];
let (key, val) = self.slice_insert(child, depth, key, val);
// The recursive call didn't introduce an extra slice, so no
// work needed to accommodate it.
if (val == NO_SLICE) {
return (0, NO_SLICE)
};
// Re-borrow the current node, after the recursive call.
let node: &mut Slice<u64> = df::borrow_mut(&mut self.id, slice_id);
// The extra slice can be accommodated in the current node
// without splitting it.
if (node.vals.length() < self.max_fan_out) {
node.keys.insert(key, off);
node.vals.insert(val, off + 1);
return (0, NO_SLICE)
};
let split_at = node.vals.length() / 2;
let mut next = Slice {
prev: slice_id,
next: node.next,
keys: node.keys.pop_until(split_at),
vals: node.vals.pop_until(split_at),
};
// SAFETY: `node` is guaranteed to have a key to pop after
// having `next` split off from it, because:
//
// split_at
// = length(node.vals) / 2
// >= self.max_fan_out / 2
// >= MIN_FAN_OUT / 2
// >= 4 / 2
// = 2
//
// Meaning there will be at least 2 elements left in the key
// vector after the split -- one to pop here, and then one to
// leave behind to ensure the remaining node is at least
// binary (not vestigial).
let pivot = node.keys.pop_back();
if (key < pivot) {
node.keys.insert(key, off);
node.vals.insert(val, off + 1);
} else {
next.keys.insert(key, off - split_at);
next.vals.insert(val, off - split_at + 1);
};
(pivot, self.alloc(next))
}
slice_remove
Remove key
from the slice at ID slice_id
with depth
depth
, in self
.
prev_id
and next_id
are the IDs of slices either side of
slice_id
that share the same parent, to be used for
redistribution and merging.
Aborts if key
does not exist within the slice.
fun slice_remove<E: store>(self: &mut big_vector::BigVector<E>, prev_id: u64, prev_key: u128, slice_id: u64, next_key: u128, next_id: u64, depth: u8, key: u128): (E, u8, u128)
fun slice_remove<E: store>(
self: &mut BigVector<E>,
prev_id: u64,
prev_key: u128,
slice_id: u64,
next_key: u128,
next_id: u64,
depth: u8,
key: u128,
): (E, u8, u128) {
if (depth == 0) {
self.leaf_remove(
prev_id,
prev_key,
slice_id,
next_key,
next_id,
key,
)
} else {
self.node_remove(
prev_id,
prev_key,
slice_id,
next_key,
next_id,
depth - 1,
key,
)
}
}
leaf_remove
Like slice_remove
but you know that slice_id
points to a
leaf.
fun leaf_remove<E: store>(self: &mut big_vector::BigVector<E>, prev_id: u64, prev_key: u128, slice_id: u64, next_key: u128, next_id: u64, key: u128): (E, u8, u128)
fun leaf_remove<E: store>(
self: &mut BigVector<E>,
prev_id: u64,
prev_key: u128,
slice_id: u64,
next_key: u128,
next_id: u64,
key: u128,
): (E, u8, u128) {
let leaf: &mut Slice<E> = df::borrow_mut(&mut self.id, slice_id);
let off = leaf.bisect_left(key);
if (off >= leaf.keys.length()) {
abort ENotFound
};
if (key != leaf.keys[off]) {
abort ENotFound
};
leaf.keys.remove(off);
let val = leaf.vals.remove(off);
let remaining = leaf.vals.length();
let min_slice_size = self.max_slice_size / 2;
if (remaining >= min_slice_size) {
return (val, RM_FIX_NOTHING, 0)
};
// Try redistribution with a neighbour
if (prev_id != NO_SLICE) {
let prev: &Slice<E> = df::borrow(&self.id, prev_id);
if (prev.vals.length() > min_slice_size) {
return (
val,
RM_FIX_STEAL_L,
self.slice_redistribute<E, E>(
prev_id,
prev_key,
slice_id,
),
)
}
};
if (next_id != NO_SLICE) {
let next: &Slice<E> = df::borrow(&self.id, next_id);
if (next.vals.length() > min_slice_size) {
return (
val,
RM_FIX_STEAL_R,
self.slice_redistribute<E, E>(
slice_id,
next_key,
next_id,
),
)
}
};
// Try merging with a neighbour
if (prev_id != NO_SLICE) {
self.slice_merge<E, E>(prev_id, prev_key, slice_id);
return (val, RM_FIX_MERGE_L, 0)
};
if (next_id != NO_SLICE) {
self.slice_merge<E, E>(slice_id, next_key, next_id);
return (val, RM_FIX_MERGE_R, 0)
};
// Neither neighbour exists, must be the root -- check whether
// it's empty.
if (remaining == 0) {
(val, RM_FIX_EMPTY, 0)
} else {
(val, RM_FIX_NOTHING, 0)
}
}
node_remove
Like slice_remove
but you know that slice_id
points to an
interior node, and depth
refers to the depth of its child
nodes.
fun node_remove<E: store>(self: &mut big_vector::BigVector<E>, prev_id: u64, prev_key: u128, slice_id: u64, next_key: u128, next_id: u64, depth: u8, key: u128): (E, u8, u128)
fun node_remove<E: store>(
self: &mut BigVector<E>,
prev_id: u64,
prev_key: u128,
slice_id: u64,
next_key: u128,
next_id: u64,
depth: u8,
key: u128,
): (E, u8, u128) {
let node: &Slice<u64> = df::borrow(&self.id, slice_id);
let off = node.bisect_right(key);
let child_id = node.vals[off];
let (child_prev_id, child_prev_key) = if (off == 0) {
(NO_SLICE, 0)
} else (
node.vals[off - 1],
node.keys[off - 1],
);
let (child_next_id, child_next_key) = if (off == node.keys.length()) {
(NO_SLICE, 0)
} else (
node.vals[off + 1],
node.keys[off],
);
let (val, rm_fix, pivot) = self.slice_remove(
child_prev_id,
child_prev_key,
child_id,
child_next_key,
child_next_id,
depth,
key,
);
// Re-borrow node mutably after recursive call, to perform
// fix-ups.
let node: &mut Slice<u64> = df::borrow_mut(&mut self.id, slice_id);
if (rm_fix == RM_FIX_NOTHING) {
return (val, RM_FIX_NOTHING, 0)
} else if (rm_fix == RM_FIX_STEAL_L) {
*(&mut node.keys[off - 1]) = pivot;
return (val, RM_FIX_NOTHING, 0)
} else if (rm_fix == RM_FIX_STEAL_R) {
*(&mut node.keys[off]) = pivot;
return (val, RM_FIX_NOTHING, 0)
} else if (rm_fix == RM_FIX_MERGE_L) {
node.keys.remove(off - 1);
node.vals.remove(off);
} else if (rm_fix == RM_FIX_MERGE_R) {
node.keys.remove(off);
node.vals.remove(off + 1);
} else {
abort EBadRemove
};
let remaining = node.vals.length();
let min_fan_out = self.max_fan_out / 2;
if (remaining >= min_fan_out) {
return (val, RM_FIX_NOTHING, 0)
};
// Try redistribution with a neighbour
if (prev_id != NO_SLICE) {
let prev: &Slice<u64> = df::borrow(&self.id, prev_id);
if (prev.vals.length() > min_fan_out) {
return (
val,
RM_FIX_STEAL_L,
self.slice_redistribute<E, u64>(
prev_id,
prev_key,
slice_id,
),
)
}
};
if (next_id != NO_SLICE) {
let next: &Slice<u64> = df::borrow(&self.id, next_id);
if (next.vals.length() > min_fan_out) {
return (
val,
RM_FIX_STEAL_R,
self.slice_redistribute<E, u64>(
slice_id,
next_key,
next_id,
),
)
}
};
// Try merging with a neighbour
if (prev_id != NO_SLICE) {
self.slice_merge<E, u64>(prev_id, prev_key, slice_id);
return (val, RM_FIX_MERGE_L, 0)
};
if (next_id != NO_SLICE) {
self.slice_merge<E, u64>(slice_id, next_key, next_id);
return (val, RM_FIX_MERGE_R, 0)
};
// Neither neighbour exists, must be the root. As we are
// dealing with an interior node, it is considered "empty"
// when it has only one child (which can replace it), and it
// is an error for it to be completely empty.
if (remaining == 0) {
abort EBadRemove
} else if (remaining == 1) {
(val, RM_FIX_EMPTY, 0)
} else {
(val, RM_FIX_NOTHING, 0)
}
}
slice_redistribute
Redistribute the elements in left_id
and right_id
separated by pivot
, evenly between each other. Returns the
new pivot element between the two slices.
Aborts if left and right are not adjacent slices.
fun slice_redistribute<E: store, F: store>(self: &mut big_vector::BigVector<E>, left_id: u64, pivot: u128, right_id: u64): u128
fun slice_redistribute<E: store, F: store>(
self: &mut BigVector<E>,
left_id: u64,
pivot: u128,
right_id: u64,
): u128 {
// Remove the slices from `self` to make it easier to
// manipulate both of them simultaneously.
let left: Slice<F> = df::remove(&mut self.id, left_id);
let right: Slice<F> = df::remove(&mut self.id, right_id);
assert!(left.next == right_id, ENotAdjacent);
assert!(right.prev == left_id, ENotAdjacent);
let is_leaf = left.is_leaf();
let Slice {
prev: lprev,
next: lnext,
keys: mut lkeys,
vals: mut lvals,
} = left;
let Slice {
prev: rprev,
next: rnext,
keys: rkeys,
vals: rvals,
} = right;
let old_l_len = lvals.length();
let old_r_len = rvals.length();
let total_len = old_l_len + old_r_len;
let new_l_len = total_len / 2;
let new_r_len = total_len - new_l_len;
// Detect whether the redistribution is left-to-right or right-to-left.
let left_to_right = if (new_l_len < old_l_len) {
true
} else if (new_r_len < old_r_len) {
false
} else {
abort EBadRedistribution
};
// Redistribute values
let (lvals, rvals) = if (left_to_right) {
let mut mvals = lvals.pop_until(new_l_len);
mvals.append(rvals);
(lvals, mvals)
} else {
let mut mvals = rvals;
let rvals = mvals.pop_n(new_r_len);
lvals.append(mvals);
(lvals, rvals)
};
// Redistribute keys and move pivot.
//
// The pivot moves from the left side to the right side of the
// middle section depending on whether the keys are from left
// to right or vice versa.
//
// The pivot also changes from inclusive to exclusive based on
// whether the slices in question are leaves or not.
//
// When handling interior nodes, the previous pivot needs to
// be incorporated during this process.
let (lkeys, pivot, rkeys) = if (is_leaf && left_to_right) {
let mut mkeys = lkeys.pop_until(new_l_len);
let pivot = mkeys[0];
mkeys.append(rkeys);
(lkeys, pivot, mkeys)
} else if (is_leaf && !left_to_right) {
let mut mkeys = rkeys;
let rkeys = mkeys.pop_n(new_r_len);
let pivot = rkeys[0];
lkeys.append(mkeys);
(lkeys, pivot, rkeys)
} else if (!is_leaf && left_to_right) {
// [left, new-pivot, mid] old-pivot [right]
// ... becomes ...
// [left] new-pivot [mid, old-pivot, right]
let mut mkeys = lkeys.pop_until(new_l_len);
mkeys.push_back(pivot);
mkeys.append(rkeys);
let pivot = lkeys.pop_back();
(lkeys, pivot, mkeys)
} else {
// [left] old-pivot [mid, new-pivot, right]
// ... becomes ...
// [left, old-pivot, mid] new-pivot [right]
lkeys.push_back(pivot);
let mut mkeys = rkeys;
let rkeys = mkeys.pop_n(new_r_len - 1);
let pivot = mkeys.pop_back();
lkeys.append(mkeys);
(lkeys, pivot, rkeys)
};
// Add the slices back to self.
df::add(
&mut self.id,
left_id,
Slice { prev: lprev, next: lnext, keys: lkeys, vals: lvals },
);
df::add(
&mut self.id,
right_id,
Slice { prev: rprev, next: rnext, keys: rkeys, vals: rvals },
);
pivot
}
slice_merge
Merge the right_id
slice into left_id
(represented by
their IDs). Assumes that left_id
and right_id
are adjacent
slices, separated by pivot
, and aborts if this is not the
case.
Upon success, left_id
contains all the elements of both
slices, and the right_id
slice has been removed from the
vector.
fun slice_merge<E: store, F: store>(self: &mut big_vector::BigVector<E>, left_id: u64, pivot: u128, right_id: u64)
fun slice_merge<E: store, F: store>(
self: &mut BigVector<E>,
left_id: u64,
pivot: u128,
right_id: u64,
) {
let right: Slice<F> = df::remove(&mut self.id, right_id);
let left: &mut Slice<F> = df::borrow_mut(&mut self.id, left_id);
assert!(left.next == right_id, ENotAdjacent);
assert!(right.prev == left_id, ENotAdjacent);
if (!left.is_leaf()) {
left.keys.push_back(pivot);
};
let Slice { prev: _, next, keys, vals } = right;
left.keys.append(keys);
left.vals.append(vals);
left.next = next;
if (next != NO_SLICE) {
let next: &mut Slice<F> = df::borrow_mut(&mut self.id, next);
next.prev = left_id;
}
}