Module 0x1::big_vector
- Struct
BigVector - Resource
Ghost$initial_end_index - Constants
- Function
empty - Function
singleton - Function
destroy_empty - Function
destroy - Function
borrow - Function
borrow_mut - Function
append - Function
push_back - Function
pop_back - Function
remove - Function
swap_remove - Function
swap - Function
reverse - Function
index_of - Function
contains - Function
to_vector - Function
length - Function
is_empty - Specification
use 0x1::error;
use 0x1::table_with_length;
use 0x1::vector;
Struct 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
Resource Ghost$initial_end_index
struct Ghost$initial_end_index has copy, drop, store, key
Fields
-
v: u64
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;
Function 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,
}
}
Function 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
}
Function 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();
}
Function 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();
}
Function borrow
Acquire an immutable reference to the ith 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)
}
Function borrow_mut
Return a mutable reference to the ith 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)
}
Function 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;
}
spec {
invariant i <= half_other_len;
invariant other.length() == other_len - i;
invariant self.length() == length(old(self)) + i;
};
while (i < other_len) {
self.push_back(other.pop_back());
i += 1;
}
spec {
invariant half_other_len <= i && i <= other_len;
invariant other.length() == other_len - i;
invariant self.length() == length(old(self)) + i;
};
other.destroy_empty();
}
Function 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;
}
Function 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
}
Function 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;
spec {
update initial_end_index = self.end_index;
};
while (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 {
invariant 1 <= cur_bucket_index && cur_bucket_index <= num_buckets;
invariant self.end_index == old(self).end_index - 1;
invariant self.bucket_size == old(self).bucket_size;
invariant table_with_length::spec_len(self.buckets) == num_buckets;
invariant forall j in 0..table_with_length::spec_len(self.buckets):
table_with_length::spec_contains(self.buckets, j);
};
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
}
Function swap_remove
Swap the ith 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
}
Function 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);
}
Function 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();
}
Function 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;
}
spec {
invariant bucket_index <= num_buckets;
// All buckets checked so far contain no element equal to val.
invariant forall j in 0..bucket_index:
(forall k in 0..len(table_with_length::spec_get(self.buckets, j)):
table_with_length::spec_get(self.buckets, j)[k] != val);
};
(false, 0)
}
Function 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
}
Function 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
}
Function length
Return the length of the vector.
public fun length<T>(self: &big_vector::BigVector<T>): u64
Function 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
Specification
global initial_end_index: u64;
Struct 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 forall i: u64 {spec_table_contains(buckets, i)}: spec_table_contains(buckets, i) ==> i < spec_table_len(buckets);
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 {spec_table_contains(buckets, i)} 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);
Function 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;
Function 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;
Function destroy_empty
public fun destroy_empty<T>(self: big_vector::BigVector<T>)
aborts_if !self.is_empty();
Function borrow
public fun borrow<T>(self: &big_vector::BigVector<T>, i: u64): &T
aborts_if i >= self.length();
ensures result == spec_at(self, i);
Function 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);
Function append
public fun append<T: store>(self: &mut big_vector::BigVector<T>, other: big_vector::BigVector<T>)
ensures self.length() == old(self.length()) + other.length();
Function push_back
public fun push_back<T: store>(self: &mut big_vector::BigVector<T>, val: T)
pragma opaque;
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;
}
Function pop_back
public fun pop_back<T>(self: &mut big_vector::BigVector<T>): T
pragma opaque;
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);
ensures forall b in 0..spec_table_len(self.buckets):
forall p in 0..len(table_with_length::spec_get(self.buckets, b)):
table_with_length::spec_get(self.buckets, b)[p] ==
table_with_length::spec_get(old(self).buckets, b)[p];
ensures self.bucket_size == old(self).bucket_size;
Function remove
public fun remove<T>(self: &mut big_vector::BigVector<T>, i: u64): T
pragma opaque;
aborts_if i >= self.length();
ensures self.length() == old(self.length()) - 1;
ensures self.bucket_size == old(self.bucket_size);
ensures result == spec_at(old(self), i);
Function swap_remove
public fun swap_remove<T>(self: &mut big_vector::BigVector<T>, i: u64): T
pragma opaque;
aborts_if i >= self.length();
ensures self.length() == old(self).length() - 1;
ensures result == spec_at(old(self), i);
Function swap
public fun swap<T>(self: &mut big_vector::BigVector<T>, i: u64, j: u64)
pragma opaque;
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);
Function index_of
public fun index_of<T>(self: &big_vector::BigVector<T>, val: &T): (bool, u64)
pragma opaque;
aborts_if false;
ensures result_1 ==> result_2 < self.length();
ensures result_1 ==> spec_at(self, result_2) == val;
ensures !result_1 ==> (forall j in 0..spec_table_len(self.buckets):
forall k in 0..len(table_with_length::spec_get(self.buckets, j)):
table_with_length::spec_get(self.buckets, j)[k] != val);
Function contains
public fun contains<T>(self: &big_vector::BigVector<T>, val: &T): bool
aborts_if 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]
}