big_vector - [mainnet]
use 0x1::error;use 0x1::table_with_length;use 0x1::vector;
Constants
Vector index is out of bounds
const EINDEX_OUT_OF_BOUNDS: u64 = 1;
Cannot pop back from an empty vector
const EVECTOR_EMPTY: u64 = 3;
Cannot destroy a non-empty vector
const EVECTOR_NOT_EMPTY: u64 = 2;
bucket_size cannot be 0
const EZERO_BUCKET_SIZE: u64 = 4;
Structs
BigVector
A scalable vector implementation based on tables where elements are grouped into buckets.
Each bucket has a capacity of bucket_size
elements.
struct BigVector<T> has store
Fields
-
buckets: table_with_length::TableWithLength<u64, vector<T>>
-
end_index: u64
-
bucket_size: u64
Functions
empty
Regular Vector API Create an empty vector.
public(friend) fun empty<T: store>(bucket_size: u64): big_vector::BigVector<T>
Implementation
friend fun empty<T: store>(bucket_size: u64): BigVector<T> { assert!(bucket_size > 0, error::invalid_argument(EZERO_BUCKET_SIZE)); BigVector { buckets: table_with_length::new(), end_index: 0, bucket_size, }}
singleton
Create a vector of length 1 containing the passed in element.
public(friend) fun singleton<T: store>(element: T, bucket_size: u64): big_vector::BigVector<T>
Implementation
friend fun singleton<T: store>(element: T, bucket_size: u64): BigVector<T> { let v = empty(bucket_size); v.push_back(element); v}
destroy_empty
Destroy the vector self
.
Aborts if self
is not empty.
public fun destroy_empty<T>(self: big_vector::BigVector<T>)
Implementation
public fun destroy_empty<T>(self: BigVector<T>) { assert!(self.is_empty(), error::invalid_argument(EVECTOR_NOT_EMPTY)); let BigVector { buckets, end_index: _, bucket_size: _ } = self; buckets.destroy_empty();}
destroy
Destroy the vector self
if T has drop
public fun destroy<T: drop>(self: big_vector::BigVector<T>)
Implementation
public fun destroy<T: drop>(self: BigVector<T>) { let BigVector { buckets, end_index, bucket_size: _ } = self; let i = 0; while (end_index > 0) { let num_elements = buckets.remove(i).length(); end_index -= num_elements; i += 1; }; buckets.destroy_empty();}
borrow
Acquire an immutable reference to the i
th element of the vector self
.
Aborts if i
is out of bounds.
public fun borrow<T>(self: &big_vector::BigVector<T>, i: u64): &T
Implementation
public fun borrow<T>(self: &BigVector<T>, i: u64): &T { assert!(i < self.length(), error::invalid_argument(EINDEX_OUT_OF_BOUNDS)); self.buckets.borrow(i / self.bucket_size).borrow(i % self.bucket_size)}
borrow_mut
Return a mutable reference to the i
th element in the vector self
.
Aborts if i
is out of bounds.
public fun borrow_mut<T>(self: &mut big_vector::BigVector<T>, i: u64): &mut T
Implementation
public fun borrow_mut<T>(self: &mut BigVector<T>, i: u64): &mut T { assert!(i < self.length(), error::invalid_argument(EINDEX_OUT_OF_BOUNDS)); self.buckets.borrow_mut(i / self.bucket_size).borrow_mut(i % self.bucket_size)}
append
Empty and destroy the other vector, and push each of the elements in the other vector onto the self vector in the same order as they occurred in other. Disclaimer: This function is costly. Use it at your own discretion.
public fun append<T: store>(self: &mut big_vector::BigVector<T>, other: big_vector::BigVector<T>)
Implementation
public fun append<T: store>(self: &mut BigVector<T>, other: BigVector<T>) { let other_len = other.length(); let half_other_len = other_len / 2; let i = 0; while (i < half_other_len) { self.push_back(other.swap_remove(i)); i += 1; }; while (i < other_len) { self.push_back(other.pop_back()); i += 1; }; other.destroy_empty();}
push_back
Add element val
to the end of the vector self
. It grows the buckets when the current buckets are full.
This operation will cost more gas when it adds new bucket.
public fun push_back<T: store>(self: &mut big_vector::BigVector<T>, val: T)
Implementation
public fun push_back<T: store>(self: &mut BigVector<T>, val: T) { let num_buckets = self.buckets.length(); if (self.end_index == num_buckets * self.bucket_size) { self.buckets.add(num_buckets, vector::empty()); self.buckets.borrow_mut(num_buckets).push_back(val); } else { self.buckets.borrow_mut(num_buckets - 1).push_back(val); }; self.end_index += 1;}
pop_back
Pop an element from the end of vector self
. It doesn’t shrink the buckets even if they’re empty.
Call shrink_to_fit
explicity to deallocate empty buckets.
Aborts if self
is empty.
public fun pop_back<T>(self: &mut big_vector::BigVector<T>): T
Implementation
public fun pop_back<T>(self: &mut BigVector<T>): T { assert!(!self.is_empty(), error::invalid_state(EVECTOR_EMPTY)); let num_buckets = self.buckets.length(); let last_bucket = self.buckets.borrow_mut(num_buckets - 1); let val = last_bucket.pop_back(); // Shrink the table if the last vector is empty. if (last_bucket.is_empty()) { move last_bucket; self.buckets.remove(num_buckets - 1).destroy_empty(); }; self.end_index -= 1; val}
remove
Remove the element at index i in the vector v and return the owned value that was previously stored at i in self. All elements occurring at indices greater than i will be shifted down by 1. Will abort if i is out of bounds. Disclaimer: This function is costly. Use it at your own discretion.
public fun remove<T>(self: &mut big_vector::BigVector<T>, i: u64): T
Implementation
public fun remove<T>(self: &mut BigVector<T>, i: u64): T { let len = self.length(); assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS)); let num_buckets = self.buckets.length(); let cur_bucket_index = i / self.bucket_size + 1; let cur_bucket = self.buckets.borrow_mut(cur_bucket_index - 1); let res = cur_bucket.remove(i % self.bucket_size); self.end_index -= 1; move cur_bucket; while ({ spec { invariant cur_bucket_index <= num_buckets; invariant table_with_length::spec_len(self.buckets) == num_buckets; }; (cur_bucket_index < num_buckets) }) { // remove one element from the start of current vector let cur_bucket = self.buckets.borrow_mut(cur_bucket_index); let t = cur_bucket.remove(0); move cur_bucket; // and put it at the end of the last one let prev_bucket = self.buckets.borrow_mut(cur_bucket_index - 1); prev_bucket.push_back(t); cur_bucket_index += 1; }; spec { assert cur_bucket_index == num_buckets; };
// Shrink the table if the last vector is empty. let last_bucket = self.buckets.borrow_mut(num_buckets - 1); if (last_bucket.is_empty()) { move last_bucket; self.buckets.remove(num_buckets - 1).destroy_empty(); };
res}
swap_remove
Swap the i
th element of the vector self
with the last element and then pop the vector.
This is O(1), but does not preserve ordering of elements in the vector.
Aborts if i
is out of bounds.
public fun swap_remove<T>(self: &mut big_vector::BigVector<T>, i: u64): T
Implementation
public fun swap_remove<T>(self: &mut BigVector<T>, i: u64): T { assert!(i < self.length(), error::invalid_argument(EINDEX_OUT_OF_BOUNDS)); let last_val = self.pop_back(); // if the requested value is the last one, return it if (self.end_index == i) { return last_val }; // because the lack of mem::swap, here we swap remove the requested value from the bucket // and append the last_val to the bucket then swap the last bucket val back let bucket = self.buckets.borrow_mut(i / self.bucket_size); let bucket_len = bucket.length(); let val = bucket.swap_remove(i % self.bucket_size); bucket.push_back(last_val); bucket.swap(i % self.bucket_size, bucket_len - 1); val}
swap
Swap the elements at the i’th and j’th indices in the vector self. Will abort if either of i or j are out of bounds for self.
public fun swap<T>(self: &mut big_vector::BigVector<T>, i: u64, j: u64)
Implementation
public fun swap<T>(self: &mut BigVector<T>, i: u64, j: u64) { assert!(i < self.length() && j < self.length(), error::invalid_argument(EINDEX_OUT_OF_BOUNDS)); let i_bucket_index = i / self.bucket_size; let j_bucket_index = j / self.bucket_size; let i_vector_index = i % self.bucket_size; let j_vector_index = j % self.bucket_size; if (i_bucket_index == j_bucket_index) { self.buckets.borrow_mut(i_bucket_index).swap(i_vector_index, j_vector_index); return }; // If i and j are in different buckets, take the buckets out first for easy mutation. let bucket_i = self.buckets.remove(i_bucket_index); let bucket_j = self.buckets.remove(j_bucket_index); // Get the elements from buckets by calling `swap_remove`. let element_i = bucket_i.swap_remove(i_vector_index); let element_j = bucket_j.swap_remove(j_vector_index); // Swap the elements and push back to the other bucket. bucket_i.push_back(element_j); bucket_j.push_back(element_i); let last_index_in_bucket_i = bucket_i.length() - 1; let last_index_in_bucket_j = bucket_j.length() - 1; // Re-position the swapped elements to the right index. bucket_i.swap(i_vector_index, last_index_in_bucket_i); bucket_j.swap(j_vector_index, last_index_in_bucket_j); // Add back the buckets. self.buckets.add(i_bucket_index, bucket_i); self.buckets.add(j_bucket_index, bucket_j);}
reverse
Reverse the order of the elements in the vector self in-place. Disclaimer: This function is costly. Use it at your own discretion.
public fun reverse<T>(self: &mut big_vector::BigVector<T>)
Implementation
public fun reverse<T>(self: &mut BigVector<T>) { let new_buckets = vector[]; let push_bucket = vector[]; let num_buckets = self.buckets.length(); let num_buckets_left = num_buckets;
while (num_buckets_left > 0) { let pop_bucket = self.buckets.remove(num_buckets_left - 1); pop_bucket.for_each_reverse(|val| { push_bucket.push_back(val); if (push_bucket.length() == self.bucket_size) { new_buckets.push_back(push_bucket); push_bucket = vector[]; }; }); num_buckets_left -= 1; };
if (push_bucket.length() > 0) { new_buckets.push_back(push_bucket); } else { push_bucket.destroy_empty(); };
new_buckets.reverse(); assert!(self.buckets.length() == 0, 0); for (i in 0..num_buckets) { self.buckets.add(i, new_buckets.pop_back()); }; new_buckets.destroy_empty();}
index_of
Return the index of the first occurrence of an element in self that is equal to e. Returns (true, index) if such an element was found, and (false, 0) otherwise. Disclaimer: This function is costly. Use it at your own discretion.
public fun index_of<T>(self: &big_vector::BigVector<T>, val: &T): (bool, u64)
Implementation
public fun index_of<T>(self: &BigVector<T>, val: &T): (bool, u64) { let num_buckets = self.buckets.length(); let bucket_index = 0; while (bucket_index < num_buckets) { let cur = self.buckets.borrow(bucket_index); let (found, i) = cur.index_of(val); if (found) { return (true, bucket_index * self.bucket_size + i) }; bucket_index += 1; }; (false, 0)}
contains
Return if an element equal to e exists in the vector self. Disclaimer: This function is costly. Use it at your own discretion.
public fun contains<T>(self: &big_vector::BigVector<T>, val: &T): bool
Implementation
public fun contains<T>(self: &BigVector<T>, val: &T): bool { if (self.is_empty()) return false; let (exist, _) = self.index_of(val); exist}
to_vector
Convert a big vector to a native vector, which is supposed to be called mostly by view functions to get an atomic view of the whole vector. Disclaimer: This function may be costly as the big vector may be huge in size. Use it at your own discretion.
public fun to_vector<T: copy>(self: &big_vector::BigVector<T>): vector<T>
Implementation
public fun to_vector<T: copy>(self: &BigVector<T>): vector<T> { let res = vector[]; let num_buckets = self.buckets.length(); for (i in 0..num_buckets) { res.append(*self.buckets.borrow(i)); }; res}
length
Return the length of the vector.
public fun length<T>(self: &big_vector::BigVector<T>): u64
Implementation
public fun length<T>(self: &BigVector<T>): u64 { self.end_index}
is_empty
Return true
if the vector v
has no elements and false
otherwise.
public fun is_empty<T>(self: &big_vector::BigVector<T>): bool
Implementation
public fun is_empty<T>(self: &BigVector<T>): bool { self.length() == 0}
Specification
BigVector
struct BigVector<T> has store
-
buckets: table_with_length::TableWithLength<u64, vector<T>>
-
end_index: u64
-
bucket_size: u64
invariant bucket_size != 0;invariant spec_table_len(buckets) == 0 ==> end_index == 0;invariant end_index == 0 ==> spec_table_len(buckets) == 0;invariant end_index <= spec_table_len(buckets) * bucket_size;invariant spec_table_len(buckets) == 0 || (forall i in 0..spec_table_len(buckets)-1: len(table_with_length::spec_get(buckets, i)) == bucket_size);invariant spec_table_len(buckets) == 0 || len(table_with_length::spec_get(buckets, spec_table_len(buckets) -1 )) <= bucket_size;invariant forall i in 0..spec_table_len(buckets): spec_table_contains(buckets, i);invariant spec_table_len(buckets) == (end_index + bucket_size - 1) / bucket_size;invariant (spec_table_len(buckets) == 0 && end_index == 0) || (spec_table_len(buckets) != 0 && ((spec_table_len(buckets) - 1) * bucket_size) + (len(table_with_length::spec_get(buckets, spec_table_len(buckets) - 1))) == end_index);invariant forall i: u64 where i >= spec_table_len(buckets): { !spec_table_contains(buckets, i)};invariant forall i: u64 where i < spec_table_len(buckets): { spec_table_contains(buckets, i)};invariant spec_table_len(buckets) == 0 || (len(table_with_length::spec_get(buckets, spec_table_len(buckets) - 1)) > 0);
empty
public(friend) fun empty<T: store>(bucket_size: u64): big_vector::BigVector<T>
aborts_if bucket_size == 0;ensures result.length() == 0;ensures result.bucket_size == bucket_size;
singleton
public(friend) fun singleton<T: store>(element: T, bucket_size: u64): big_vector::BigVector<T>
aborts_if bucket_size == 0;ensures result.length() == 1;ensures result.bucket_size == bucket_size;
destroy_empty
public fun destroy_empty<T>(self: big_vector::BigVector<T>)
aborts_if !self.is_empty();
borrow
public fun borrow<T>(self: &big_vector::BigVector<T>, i: u64): &T
aborts_if i >= self.length();ensures result == spec_at(self, i);
borrow_mut
public fun borrow_mut<T>(self: &mut big_vector::BigVector<T>, i: u64): &mut T
aborts_if i >= self.length();ensures result == spec_at(self, i);
append
public fun append<T: store>(self: &mut big_vector::BigVector<T>, other: big_vector::BigVector<T>)
pragma verify=false;
push_back
public fun push_back<T: store>(self: &mut big_vector::BigVector<T>, val: T)
let num_buckets = spec_table_len(self.buckets);include PushbackAbortsIf<T>;ensures self.length() == old(self).length() + 1;ensures self.end_index == old(self.end_index) + 1;ensures spec_at(self, self.end_index-1) == val;ensures forall i in 0..self.end_index-1: spec_at(self, i) == spec_at(old(self), i);ensures self.bucket_size == old(self).bucket_size;
schema PushbackAbortsIf<T> { self: BigVector<T>; let num_buckets = spec_table_len(self.buckets); aborts_if num_buckets * self.bucket_size > MAX_U64; aborts_if self.end_index + 1 > MAX_U64;}
pop_back
public fun pop_back<T>(self: &mut big_vector::BigVector<T>): T
aborts_if self.is_empty();ensures self.length() == old(self).length() - 1;ensures result == old(spec_at(self, self.end_index-1));ensures forall i in 0..self.end_index: spec_at(self, i) == spec_at(old(self), i);
remove
public fun remove<T>(self: &mut big_vector::BigVector<T>, i: u64): T
pragma verify=false;
swap_remove
public fun swap_remove<T>(self: &mut big_vector::BigVector<T>, i: u64): T
pragma verify_duration_estimate = 120;aborts_if i >= self.length();ensures self.length() == old(self).length() - 1;ensures result == spec_at(old(self), i);
swap
public fun swap<T>(self: &mut big_vector::BigVector<T>, i: u64, j: u64)
pragma verify_duration_estimate = 1000;aborts_if i >= self.length() || j >= self.length();ensures self.length() == old(self).length();ensures spec_at(self, i) == spec_at(old(self), j);ensures spec_at(self, j) == spec_at(old(self), i);ensures forall idx in 0..self.length() where idx != i && idx != j: spec_at(self, idx) == spec_at(old(self), idx);
reverse
public fun reverse<T>(self: &mut big_vector::BigVector<T>)
pragma verify=false;
index_of
public fun index_of<T>(self: &big_vector::BigVector<T>, val: &T): (bool, u64)
pragma verify=false;
fun spec_table_len<K, V>(t: TableWithLength<K, V>): u64 { table_with_length::spec_len(t)}
fun spec_table_contains<K, V>(t: TableWithLength<K, V>, k: K): bool { table_with_length::spec_contains(t, k)}
fun spec_at<T>(v: BigVector<T>, i: u64): T { let bucket = i / v.bucket_size; let idx = i % v.bucket_size; let v = table_with_length::spec_get(v.buckets, bucket); v[idx]}