diff --git a/aptos-move/framework/aptos-stdlib/doc/big_vector.md b/aptos-move/framework/aptos-stdlib/doc/big_vector.md
new file mode 100644
index 0000000000000..6f1021d32d63e
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/doc/big_vector.md
@@ -0,0 +1,650 @@
+
+
+
+# Module `0x1::big_vector`
+
+
+
+- [Struct `BigVector`](#0x1_big_vector_BigVector)
+- [Constants](#@Constants_0)
+- [Function `empty`](#0x1_big_vector_empty)
+- [Function `singleton`](#0x1_big_vector_singleton)
+- [Function `destroy_empty`](#0x1_big_vector_destroy_empty)
+- [Function `borrow`](#0x1_big_vector_borrow)
+- [Function `borrow_mut`](#0x1_big_vector_borrow_mut)
+- [Function `append`](#0x1_big_vector_append)
+- [Function `push_back`](#0x1_big_vector_push_back)
+- [Function `pop_back`](#0x1_big_vector_pop_back)
+- [Function `remove`](#0x1_big_vector_remove)
+- [Function `swap_remove`](#0x1_big_vector_swap_remove)
+- [Function `swap`](#0x1_big_vector_swap)
+- [Function `range_reverse`](#0x1_big_vector_range_reverse)
+- [Function `reverse`](#0x1_big_vector_reverse)
+- [Function `index_of`](#0x1_big_vector_index_of)
+- [Function `contains`](#0x1_big_vector_contains)
+- [Function `length`](#0x1_big_vector_length)
+- [Function `is_empty`](#0x1_big_vector_is_empty)
+
+
+
use 0x1::debug;
+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
+
+-
+
+
+-
+
num_buckets: u64
+
+-
+
+
+-
+
bucket_size: u64
+
+-
+
+
+
+
+
+
+
+
+
+## Constants
+
+
+
+
+Vector index is out of bounds
+
+
+const EINDEX_OUT_OF_BOUNDS: u64 = 1;
+
+
+
+
+
+
+Vector is full
+
+
+const EOUT_OF_CAPACITY: u64 = 2;
+
+
+
+
+
+
+Cannot destroy a non-empty vector
+
+
+const EVECTOR_NOT_EMPTY: u64 = 3;
+
+
+
+
+
+
+## Function `empty`
+
+Regular Vector API
+Create an empty vector.
+
+
+public fun empty<T: store>(bucket_size: u64): big_vector::BigVector<T>
+
+
+
+
+
+Implementation
+
+
+public fun empty<T: store>(bucket_size: u64): BigVector<T> {
+ assert!(bucket_size > 0, 0);
+ BigVector {
+ buckets: table_with_length::new(),
+ end_index: 0,
+ num_buckets: 0,
+ bucket_size,
+ }
+}
+
+
+
+
+
+
+
+
+## Function `singleton`
+
+Create a vector of length 1 containing the passed in element.
+
+
+public fun singleton<T: store>(e: T, bucket_size: u64): big_vector::BigVector<T>
+
+
+
+
+
+Implementation
+
+
+public fun singleton<T: store>(e: T, bucket_size: u64): BigVector<T> {
+ let v = empty(bucket_size);
+ push_back(&mut v, e);
+ v
+}
+
+
+
+
+
+
+
+
+## Function `destroy_empty`
+
+Destroy the vector v
.
+Aborts if v
is not empty.
+
+
+public fun destroy_empty<T>(v: big_vector::BigVector<T>)
+
+
+
+
+
+Implementation
+
+
+public fun destroy_empty<T>(v: BigVector<T>) {
+ assert!(is_empty(&v), error::invalid_argument(EVECTOR_NOT_EMPTY));
+ let BigVector { buckets, end_index: _, num_buckets: _, bucket_size: _ } = v;
+ table_with_length::destroy_empty(buckets);
+}
+
+
+
+
+
+
+
+
+## Function `borrow`
+
+Acquire an immutable reference to the i
th element of the vector v
.
+Aborts if i
is out of bounds.
+
+
+public fun borrow<T>(v: &big_vector::BigVector<T>, i: u64): &T
+
+
+
+
+
+Implementation
+
+
+public fun borrow<T>(v: &BigVector<T>, i: u64): &T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ debug::print(&i);
+ vector::borrow(table_with_length::borrow(&v.buckets, i / v.bucket_size), i % v.bucket_size)
+}
+
+
+
+
+
+
+
+
+## Function `borrow_mut`
+
+Return a mutable reference to the i
th element in the vector v
.
+Aborts if i
is out of bounds.
+
+
+public fun borrow_mut<T>(v: &mut big_vector::BigVector<T>, i: u64): &mut T
+
+
+
+
+
+Implementation
+
+
+public fun borrow_mut<T>(v: &mut BigVector<T>, i: u64): &mut T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ vector::borrow_mut(table_with_length::borrow_mut(&mut v.buckets, i / v.bucket_size), i % v.bucket_size)
+}
+
+
+
+
+
+
+
+
+## Function `append`
+
+
+
+public fun append<T: store>(lhs: &mut big_vector::BigVector<T>, other: big_vector::BigVector<T>)
+
+
+
+
+
+Implementation
+
+
+public fun append<T: store>(lhs: &mut BigVector<T>, other: BigVector<T>) {
+ let other_len = length(&other);
+ let half_other_len = other_len / 2;
+ let i = 0;
+ while (i < half_other_len) {
+ push_back(lhs, swap_remove(&mut other, i));
+ i = i + 1;
+ };
+ while (i < other_len) {
+ push_back(lhs, pop_back(&mut other));
+ };
+ destroy_empty(other);
+}
+
+
+
+
+
+
+
+
+## Function `push_back`
+
+Add element val
to the end of the vector v
. 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>(v: &mut big_vector::BigVector<T>, val: T)
+
+
+
+
+
+Implementation
+
+
+public fun push_back<T: store>(v: &mut BigVector<T>, val: T) {
+ if (v.end_index == v.num_buckets * v.bucket_size) {
+ table_with_length::add(&mut v.buckets, v.num_buckets, vector::empty());
+ v.num_buckets = v.num_buckets + 1;
+ };
+ debug::print(&val);
+ vector::push_back(table_with_length::borrow_mut(&mut v.buckets, v.num_buckets - 1), val);
+ v.end_index = v.end_index + 1;
+}
+
+
+
+
+
+
+
+
+## Function `pop_back`
+
+Pop an element from the end of vector v
. It doesn't shrink the buckets even if they're empty.
+Call shrink_to_fit
explicity to deallocate empty buckets.
+Aborts if v
is empty.
+
+
+public fun pop_back<T>(v: &mut big_vector::BigVector<T>): T
+
+
+
+
+
+Implementation
+
+
+public fun pop_back<T>(v: &mut BigVector<T>): T {
+ assert!(!is_empty(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let last_bucket = table_with_length::borrow_mut(&mut v.buckets, v.num_buckets - 1);
+ let val = vector::pop_back(last_bucket);
+ // Shrink the table if the last vector is empty.
+ if (vector::is_empty(last_bucket)) {
+ move last_bucket;
+ vector::destroy_empty(table_with_length::remove(&mut v.buckets, v.num_buckets - 1));
+ };
+ v.end_index = v.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 v.
+All elements occurring at indices greater than i will be shifted down by 1. Will abort if i is out of bounds.
+
+
+public fun remove<T>(v: &mut big_vector::BigVector<T>, i: u64): T
+
+
+
+
+
+Implementation
+
+
+public fun remove<T>(v: &mut BigVector<T>, i: u64): T {
+ let len = length(v);
+ assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let val = swap_remove(v, i);
+ if (i < len - 1) {
+ range_reverse(v, i + 1, len);
+ };
+ range_reverse(v, i, len);
+ val
+}
+
+
+
+
+
+
+
+
+## Function `swap_remove`
+
+Swap the i
th element of the vector v
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>(v: &mut big_vector::BigVector<T>, i: u64): T
+
+
+
+
+
+Implementation
+
+
+public fun swap_remove<T>(v: &mut BigVector<T>, i: u64): T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let last_val = pop_back(v);
+ // if the requested value is the last one, return it
+ if (v.end_index == i + 1) {
+ 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 = table_with_length::borrow_mut(&mut v.buckets, i / v.bucket_size);
+ let bucket_len = vector::length(bucket);
+ let val = vector::swap_remove(bucket, i % v.bucket_size);
+ vector::push_back(bucket, last_val);
+ vector::swap(bucket, i % v.bucket_size, bucket_len - 1);
+ val
+}
+
+
+
+
+
+
+
+
+## Function `swap`
+
+Swap the elements at the i'th and j'th indices in the vector v. Will abort if either of i or j are out of bounds
+for v.
+
+
+public fun swap<T>(v: &mut big_vector::BigVector<T>, i: u64, j: u64)
+
+
+
+
+
+Implementation
+
+
+public fun swap<T>(v: &mut BigVector<T>, i: u64, j: u64) {
+ assert!(i < length(v) && j < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let i_bucket_index = i / v.bucket_size;
+ let j_bucket_index = j / v.bucket_size;
+ let i_vector_index = i % v.bucket_size;
+ let j_vector_index = j % v.bucket_size;
+ if (i_bucket_index == j_bucket_index) {
+ vector::swap(table_with_length::borrow_mut(&mut v.buckets, i_bucket_index), i_vector_index, j_vector_index);
+ return
+ };
+ let bucket_i = table_with_length::remove(&mut v.buckets, i_bucket_index);
+ let bucket_j = table_with_length::remove(&mut v.buckets, j_bucket_index);
+ let element_i = vector::swap_remove(&mut bucket_i, i_vector_index);
+ let element_j = vector::swap_remove(&mut bucket_j, j_vector_index);
+ vector::push_back(&mut bucket_i, element_j);
+ vector::push_back(&mut bucket_j, element_i);
+ let last_index_in_bucket_i = vector::length(&bucket_i) - 1;
+ let last_index_in_bucket_j = vector::length(&bucket_j) - 1;
+ vector::swap(&mut bucket_i, i_vector_index, last_index_in_bucket_i);
+ vector::swap(&mut bucket_j, j_vector_index, last_index_in_bucket_j);
+ table_with_length::add(&mut v.buckets, i_bucket_index, bucket_i);
+ table_with_length::add(&mut v.buckets, j_bucket_index, bucket_j);
+}
+
+
+
+
+
+
+
+
+## Function `range_reverse`
+
+Reverse the order of the elements in range [i, j) of the vector v.
+
+
+fun range_reverse<T>(v: &mut big_vector::BigVector<T>, i: u64, j: u64)
+
+
+
+
+
+Implementation
+
+
+fun range_reverse<T>(v: &mut BigVector<T>, i: u64, j: u64) {
+ let half_len = (j - i) / 2;
+ let k = 0;
+ while (k < half_len) {
+ swap(v, i + k, j - 1 - k);
+ k = k + 1;
+ }
+}
+
+
+
+
+
+
+
+
+## Function `reverse`
+
+Reverse the order of the elements in the vector v in-place.
+
+
+public fun reverse<T>(v: &mut big_vector::BigVector<T>)
+
+
+
+
+
+Implementation
+
+
+public fun reverse<T>(v: &mut BigVector<T>) {
+ let len = length(v);
+ range_reverse(v, 0, len);
+}
+
+
+
+
+
+
+
+
+## Function `index_of`
+
+Return the index of the first occurrence of an element in v that is equal to e. Returns (true, index) if such an
+element was found, and (false, 0) otherwise.
+
+
+public fun index_of<T>(v: &big_vector::BigVector<T>, val: &T): (bool, u64)
+
+
+
+
+
+Implementation
+
+
+public fun index_of<T>(v: &BigVector<T>, val: &T): (bool, u64) {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ if (borrow(v, i) == val) {
+ return (true, i)
+ };
+ i = i + 1;
+ };
+ (false, 0)
+}
+
+
+
+
+
+
+
+
+## Function `contains`
+
+Return if an element equal to e exists in the vector v.
+
+
+public fun contains<T>(v: &big_vector::BigVector<T>, val: &T): bool
+
+
+
+
+
+Implementation
+
+
+public fun contains<T>(v: &BigVector<T>, val: &T): bool {
+ if (is_empty(v)) return false;
+ let (exist, _) = index_of(v, val);
+ exist
+}
+
+
+
+
+
+
+
+
+## Function `length`
+
+Return the length of the vector.
+
+
+public fun length<T>(v: &big_vector::BigVector<T>): u64
+
+
+
+
+
+Implementation
+
+
+public fun length<T>(v: &BigVector<T>): u64 {
+ v.end_index
+}
+
+
+
+
+
+
+
+
+## Function `is_empty`
+
+Return true
if the vector v
has no elements and false
otherwise.
+
+
+public fun is_empty<T>(v: &big_vector::BigVector<T>): bool
+
+
+
+
+
+Implementation
+
+
+public fun is_empty<T>(v: &BigVector<T>): bool {
+ length(v) == 0
+}
+
+
+
+
+
+
+
+[move-book]: https://move-language.github.io/move/introduction.html
diff --git a/aptos-move/framework/aptos-stdlib/doc/overview.md b/aptos-move/framework/aptos-stdlib/doc/overview.md
index 393fd797701ec..0a014c2e76d4a 100644
--- a/aptos-move/framework/aptos-stdlib/doc/overview.md
+++ b/aptos-move/framework/aptos-stdlib/doc/overview.md
@@ -14,6 +14,7 @@ This is the reference documentation of the Aptos standard library.
- [`0x1::any`](any.md#0x1_any)
- [`0x1::aptos_hash`](hash.md#0x1_aptos_hash)
+- [`0x1::big_vector`](big_vector.md#0x1_big_vector)
- [`0x1::bls12381`](bls12381.md#0x1_bls12381)
- [`0x1::capability`](capability.md#0x1_capability)
- [`0x1::comparator`](comparator.md#0x1_comparator)
@@ -28,6 +29,7 @@ This is the reference documentation of the Aptos standard library.
- [`0x1::ristretto255`](ristretto255.md#0x1_ristretto255)
- [`0x1::secp256k1`](secp256k1.md#0x1_secp256k1)
- [`0x1::simple_map`](simple_map.md#0x1_simple_map)
+- [`0x1::smart_vector`](smart_vector.md#0x1_smart_vector)
- [`0x1::table`](table.md#0x1_table)
- [`0x1::table_with_length`](table_with_length.md#0x1_table_with_length)
- [`0x1::type_info`](type_info.md#0x1_type_info)
diff --git a/aptos-move/framework/aptos-stdlib/doc/smart_vector.md b/aptos-move/framework/aptos-stdlib/doc/smart_vector.md
new file mode 100644
index 0000000000000..6f6a73dc25641
--- /dev/null
+++ b/aptos-move/framework/aptos-stdlib/doc/smart_vector.md
@@ -0,0 +1,673 @@
+
+
+
+# Module `0x1::smart_vector`
+
+
+
+- [Struct `SmartVector`](#0x1_smart_vector_SmartVector)
+- [Constants](#@Constants_0)
+- [Function `size_of_val`](#0x1_smart_vector_size_of_val)
+- [Function `empty`](#0x1_smart_vector_empty)
+- [Function `singleton`](#0x1_smart_vector_singleton)
+- [Function `destroy_empty`](#0x1_smart_vector_destroy_empty)
+- [Function `borrow`](#0x1_smart_vector_borrow)
+- [Function `borrow_mut`](#0x1_smart_vector_borrow_mut)
+- [Function `append`](#0x1_smart_vector_append)
+- [Function `push_back`](#0x1_smart_vector_push_back)
+- [Function `pop_back`](#0x1_smart_vector_pop_back)
+- [Function `remove`](#0x1_smart_vector_remove)
+- [Function `swap_remove`](#0x1_smart_vector_swap_remove)
+- [Function `swap`](#0x1_smart_vector_swap)
+- [Function `reverse`](#0x1_smart_vector_reverse)
+- [Function `index_of`](#0x1_smart_vector_index_of)
+- [Function `contains`](#0x1_smart_vector_contains)
+- [Function `length`](#0x1_smart_vector_length)
+- [Function `is_empty`](#0x1_smart_vector_is_empty)
+
+
+use 0x1::bcs;
+use 0x1::big_vector;
+use 0x1::error;
+use 0x1::math64;
+use 0x1::vector;
+
+
+
+
+
+
+## Struct `SmartVector`
+
+A Scalable vector implementation based on tables, elements are grouped into buckets with bucket_size
.
+The internal vectors are actually option but it is intentional because Option requires T
to be drop
but
+SmartVector
should not enforece that.
+
+
+struct SmartVector<T> has store
+
+
+
+
+
+Fields
+
+
+
+-
+
inline_inner: vector<T>
+
+-
+
+
+-
+
table_inner: vector<big_vector::BigVector<T>>
+
+-
+
+
+
+
+
+
+
+
+
+## Constants
+
+
+
+
+Vector index is out of bounds
+
+
+const EINDEX_OUT_OF_BOUNDS: u64 = 1;
+
+
+
+
+
+
+Vector is full
+
+
+const EOUT_OF_CAPACITY: u64 = 2;
+
+
+
+
+
+
+Cannot destroy a non-empty vector
+
+
+const EVECTOR_NOT_EMPTY: u64 = 3;
+
+
+
+
+
+
+## Function `size_of_val`
+
+
+
+public fun size_of_val<T>(val_ref: &T): u64
+
+
+
+
+
+Implementation
+
+
+public fun size_of_val<T>(val_ref: &T): u64 {
+ // Return vector length of vectorized BCS representation.
+ vector::length(&bcs::to_bytes(val_ref))
+}
+
+
+
+
+
+
+
+
+## Function `empty`
+
+Regular Vector API
+Create an empty vector.
+
+
+public fun empty<T: store>(): smart_vector::SmartVector<T>
+
+
+
+
+
+Implementation
+
+
+public fun empty<T: store>(): SmartVector<T> {
+ SmartVector {
+ inline_inner: vector[],
+ table_inner: vector[],
+ }
+}
+
+
+
+
+
+
+
+
+## Function `singleton`
+
+
+
+public fun singleton<T: store>(e: T): smart_vector::SmartVector<T>
+
+
+
+
+
+Implementation
+
+
+public fun singleton<T: store>(e: T): SmartVector<T> {
+ let v = empty();
+ push_back(&mut v, e);
+ v
+}
+
+
+
+
+
+
+
+
+## Function `destroy_empty`
+
+Destroy the vector v
.
+Aborts if v
is not empty.
+
+
+public fun destroy_empty<T>(v: smart_vector::SmartVector<T>)
+
+
+
+
+
+Implementation
+
+
+public fun destroy_empty<T>(v: SmartVector<T>) {
+ assert!(is_empty(&v), error::invalid_argument(EVECTOR_NOT_EMPTY));
+ let SmartVector { inline_inner, table_inner} = v;
+ vector::destroy_empty(inline_inner);
+ vector::destroy_empty(table_inner);
+}
+
+
+
+
+
+
+
+
+## Function `borrow`
+
+Acquire an immutable reference to the i
th element of the vector v
.
+Aborts if i
is out of bounds.
+
+
+public fun borrow<T>(v: &smart_vector::SmartVector<T>, i: u64): &T
+
+
+
+
+
+Implementation
+
+
+public fun borrow<T>(v: &SmartVector<T>, i: u64): &T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_inner);
+ if (i < inline_len) {
+ vector::borrow(&v.inline_inner, i)
+ } else {
+ big_vector::borrow(vector::borrow(&v.table_inner, 0), i - inline_len)
+ }
+}
+
+
+
+
+
+
+
+
+## Function `borrow_mut`
+
+Return a mutable reference to the i
th element in the vector v
.
+Aborts if i
is out of bounds.
+
+
+public fun borrow_mut<T>(v: &mut smart_vector::SmartVector<T>, i: u64): &mut T
+
+
+
+
+
+Implementation
+
+
+public fun borrow_mut<T>(v: &mut SmartVector<T>, i: u64): &mut T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_inner);
+ if (i < inline_len) {
+ vector::borrow_mut(&mut v.inline_inner, i)
+ } else {
+ big_vector::borrow_mut(vector::borrow_mut(&mut v.table_inner, 0), i - inline_len)
+ }
+}
+
+
+
+
+
+
+
+
+## Function `append`
+
+
+
+public fun append<T: store>(lhs: &mut smart_vector::SmartVector<T>, other: smart_vector::SmartVector<T>)
+
+
+
+
+
+Implementation
+
+
+public fun append<T: store>(lhs: &mut SmartVector<T>, other: SmartVector<T>) {
+ let half_len = length(&other) / 2;
+ let i = 0;
+ while (i < half_len) {
+ push_back(lhs, swap_remove(&mut other, i));
+ i = i + 1;
+ };
+ while (length(&other) > 0) {
+ push_back(lhs, pop_back(&mut other));
+ };
+ destroy_empty(other);
+}
+
+
+
+
+
+
+
+
+## Function `push_back`
+
+Add element val
to the end of the vector v
. 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>(v: &mut smart_vector::SmartVector<T>, val: T)
+
+
+
+
+
+Implementation
+
+
+public fun push_back<T: store>(v: &mut SmartVector<T>, val: T) {
+ let len = length(v);
+ let inline_len = vector::length(&v.inline_inner);
+ let val_size = size_of_val(&val);
+ if (len == inline_len) {
+ if (val_size * (inline_len + 1) < 150 /* magic number */) {
+ assert!(vector::length(&v.table_inner) == 0, 123);
+ vector::push_back(&mut v.inline_inner, val);
+ return
+ };
+ let estimated_avg_size = (size_of_val(&v.inline_inner) + val_size) / (inline_len + 1);
+ let bucket_size = max(1024 /* free_write_quota */ / estimated_avg_size, 1);
+ vector::push_back(&mut v.table_inner, big_vector::empty(bucket_size));
+ };
+ big_vector::push_back(vector::borrow_mut(&mut v.table_inner, 0), val);
+}
+
+
+
+
+
+
+
+
+## Function `pop_back`
+
+Pop an element from the end of vector v
. It doesn't shrink the buckets even if they're empty.
+Call shrink_to_fit
explicity to deallocate empty buckets.
+Aborts if v
is empty.
+
+
+public fun pop_back<T>(v: &mut smart_vector::SmartVector<T>): T
+
+
+
+
+
+Implementation
+
+
+public fun pop_back<T>(v: &mut SmartVector<T>): T {
+ assert!(!is_empty(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let table_inner = &mut v.table_inner;
+ if (vector::length(table_inner) != 0) {
+ let big_vec = vector::pop_back(table_inner);
+ let val = big_vector::pop_back(&mut big_vec);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ vector::push_back(table_inner, big_vec);
+ };
+ val
+ } else {
+ vector::pop_back(&mut v.inline_inner)
+ }
+}
+
+
+
+
+
+
+
+
+## Function `remove`
+
+
+
+public fun remove<T>(v: &mut smart_vector::SmartVector<T>, i: u64): T
+
+
+
+
+
+Implementation
+
+
+public fun remove<T>(v: &mut SmartVector<T>, i: u64): T {
+ let len = length(v);
+ assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_inner);
+ if (i < inline_len) {
+ vector::remove(&mut v.inline_inner, i)
+ } else {
+ big_vector::remove(vector::borrow_mut(&mut v.table_inner, 0), i - inline_len)
+ }
+}
+
+
+
+
+
+
+
+
+## Function `swap_remove`
+
+Swap the i
th element of the vector v
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>(v: &mut smart_vector::SmartVector<T>, i: u64): T
+
+
+
+
+
+Implementation
+
+
+public fun swap_remove<T>(v: &mut SmartVector<T>, i: u64): T {
+ let len = length(v);
+ assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_inner);
+ let table_inner = &mut v.table_inner;
+ let inline_inner = &mut v.inline_inner;
+ if (i >= inline_len) {
+ let big_vec = vector::pop_back(table_inner);
+ let val = big_vector::swap_remove(&mut big_vec, i - inline_len);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ vector::push_back(table_inner, big_vec);
+ };
+ val
+ } else {
+ let val = vector::swap_remove(inline_inner, i);
+ if (inline_len < len) {
+ let big_vec = vector::pop_back(table_inner);
+ let last_from_big_vec = big_vector::pop_back(&mut big_vec);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ vector::push_back(table_inner, big_vec);
+ };
+ vector::push_back(inline_inner, last_from_big_vec);
+ vector::swap(inline_inner, i, inline_len - 1);
+ };
+ val
+ }
+}
+
+
+
+
+
+
+
+
+## Function `swap`
+
+Swap the elements at the i'th and j'th indices in the vector v. Will abort if either of i or j are out of bounds
+for v.
+
+
+public fun swap<T: store>(v: &mut smart_vector::SmartVector<T>, i: u64, j: u64)
+
+
+
+
+
+Implementation
+
+
+public fun swap<T: store>(v: &mut SmartVector<T>, i: u64, j: u64) {
+ if (i > j) {
+ return swap(v, j, i)
+ };
+ let len = length(v);
+ assert!(j < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_inner);
+ if (i >= inline_len) {
+ big_vector::swap(vector::borrow_mut(&mut v.table_inner, 0), i - inline_len, j - inline_len);
+ } else if (j < inline_len) {
+ vector::swap(&mut v.inline_inner, i, j);
+ } else {
+ let big_vec = vector::borrow_mut(&mut v.table_inner, 0);
+ let small_vec = &mut v.inline_inner;
+ let element_i = vector::swap_remove(small_vec, i);
+ let element_j = big_vector::swap_remove(big_vec, j - inline_len);
+ vector::push_back(small_vec, element_j);
+ vector::swap(small_vec, i, inline_len - 1);
+ big_vector::push_back(big_vec, element_i);
+ big_vector::swap(big_vec, i, len - inline_len - 1);
+ }
+}
+
+
+
+
+
+
+
+
+## Function `reverse`
+
+Reverse the order of the elements in the vector v in-place.
+
+
+public fun reverse<T: store>(v: &mut smart_vector::SmartVector<T>)
+
+
+
+
+
+Implementation
+
+
+public fun reverse<T: store>(v: &mut SmartVector<T>) {
+ let i = 0;
+ let len = length(v);
+ let half_len = len / 2;
+ let k = 0;
+ while (k < half_len) {
+ swap(v, i + k, len - 1 - k);
+ k = k + 1;
+ }
+}
+
+
+
+
+
+
+
+
+## Function `index_of`
+
+Return (true, i)
if val
is in the vector v
at index i
.
+Otherwise, returns (false, 0)
.
+
+
+public fun index_of<T>(v: &smart_vector::SmartVector<T>, val: &T): (bool, u64)
+
+
+
+
+
+Implementation
+
+
+public fun index_of<T>(v: &SmartVector<T>, val: &T): (bool, u64) {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ if (borrow(v, i) == val) {
+ return (true, i)
+ };
+ i = i + 1;
+ };
+ (false, 0)
+}
+
+
+
+
+
+
+
+
+## Function `contains`
+
+Return true if val
is in the vector v
.
+
+
+public fun contains<T>(v: &smart_vector::SmartVector<T>, val: &T): bool
+
+
+
+
+
+Implementation
+
+
+public fun contains<T>(v: &SmartVector<T>, val: &T): bool {
+ if (is_empty(v)) return false;
+ let (exist, _) = index_of(v, val);
+ exist
+}
+
+
+
+
+
+
+
+
+## Function `length`
+
+Return the length of the vector.
+
+
+public fun length<T>(v: &smart_vector::SmartVector<T>): u64
+
+
+
+
+
+Implementation
+
+
+public fun length<T>(v: &SmartVector<T>): u64 {
+ vector::length(&v.inline_inner) + if (vector::is_empty(&v.table_inner)) {
+ 0
+ } else {
+ big_vector::length(vector::borrow(&v.table_inner, 0))
+ }
+}
+
+
+
+
+
+
+
+
+## Function `is_empty`
+
+Return true
if the vector v
has no elements and false
otherwise.
+
+
+public fun is_empty<T>(v: &smart_vector::SmartVector<T>): bool
+
+
+
+
+
+Implementation
+
+
+public fun is_empty<T>(v: &SmartVector<T>): bool {
+ length(v) == 0
+}
+
+
+
+
+
+
+
+[move-book]: https://move-language.github.io/move/introduction.html
diff --git a/aptos-move/move-examples/data_structures/sources/big_vector.move b/aptos-move/move-examples/data_structures/sources/big_vector.move
index ee620850e6cc3..70c1a26f78187 100644
--- a/aptos-move/move-examples/data_structures/sources/big_vector.move
+++ b/aptos-move/move-examples/data_structures/sources/big_vector.move
@@ -5,21 +5,16 @@ module aptos_std::big_vector {
/// Vector index is out of bounds
const EINDEX_OUT_OF_BOUNDS: u64 = 1;
- /// Vector is full
- const EOUT_OF_CAPACITY: u64 = 2;
/// Cannot destroy a non-empty vector
- const EVECTOR_NOT_EMPTY: u64 = 3;
+ const EVECTOR_NOT_EMPTY: u64 = 2;
+ /// bucket_size cannot be 0
+ const EZERO_BUCKET_SIZE: u64 = 3;
- /// Index of the value in the buckets.
- struct BigVectorIndex has copy, drop, store {
- bucket_index: u64,
- vec_index: u64,
- }
-
- /// A Scalable vector implementation based on tables, elements are grouped into buckets with `bucket_size`.
+ /// A scalable vector implementation based on tables where elements are grouped into buckets.
+ /// Each bucket has a capacity of `bucket_size` elements.
struct BigVector has store {
buckets: TableWithLength>,
- end_index: BigVectorIndex,
+ end_index: u64,
num_buckets: u64,
bucket_size: u64
}
@@ -27,23 +22,20 @@ module aptos_std::big_vector {
/// Regular Vector API
/// Create an empty vector.
- public fun new(bucket_size: u64): BigVector {
- assert!(bucket_size > 0, 0);
+ public fun empty(bucket_size: u64): BigVector {
+ assert!(bucket_size > 0, error::invalid_argument(EZERO_BUCKET_SIZE));
BigVector {
buckets: table_with_length::new(),
- end_index: BigVectorIndex {
- bucket_index: 0,
- vec_index: 0,
- },
+ end_index: 0,
num_buckets: 0,
bucket_size,
}
}
- /// Create an empty vector with `num_buckets` reserved.
- public fun new_with_capacity(bucket_size: u64, num_buckets: u64): BigVector {
- let v = new(bucket_size);
- reserve(&mut v, num_buckets);
+ /// Create a vector of length 1 containing the passed in element.
+ public fun singleton(e: T, bucket_size: u64): BigVector {
+ let v = empty(bucket_size);
+ push_back(&mut v, e);
v
}
@@ -51,30 +43,51 @@ module aptos_std::big_vector {
/// Aborts if `v` is not empty.
public fun destroy_empty(v: BigVector) {
assert!(is_empty(&v), error::invalid_argument(EVECTOR_NOT_EMPTY));
- shrink_to_fit(&mut v);
let BigVector { buckets, end_index: _, num_buckets: _, bucket_size: _ } = v;
table_with_length::destroy_empty(buckets);
}
+ /// Acquire an immutable reference to the `i`th element of the vector `v`.
+ /// Aborts if `i` is out of bounds.
+ public fun borrow(v: &BigVector, i: u64): &T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ vector::borrow(table_with_length::borrow(&v.buckets, i / v.bucket_size), i % v.bucket_size)
+ }
+
+ /// Return a mutable reference to the `i`th element in the vector `v`.
+ /// Aborts if `i` is out of bounds.
+ public fun borrow_mut(v: &mut BigVector, i: u64): &mut T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ vector::borrow_mut(table_with_length::borrow_mut(&mut v.buckets, i / v.bucket_size), i % v.bucket_size)
+ }
+
+ /// Empty and destroy the other vector, and push each of the elements in the other vector onto the lhs vector in the
+ /// same order as they occurred in other.
+ /// disclaimer: this function is costly. Use it at your own discretion.
+ public fun append(lhs: &mut BigVector, other: BigVector) {
+ let other_len = length(&other);
+ let half_other_len = other_len / 2;
+ let i = 0;
+ while (i < half_other_len) {
+ push_back(lhs, swap_remove(&mut other, i));
+ i = i + 1;
+ };
+ while (i < other_len) {
+ push_back(lhs, pop_back(&mut other));
+ i = i + 1;
+ };
+ destroy_empty(other);
+ }
+
/// Add element `val` to the end of the vector `v`. 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(v: &mut BigVector, val: T) {
- if (v.end_index.bucket_index == v.num_buckets) {
+ public fun push_back(v: &mut BigVector, val: T) {
+ if (v.end_index == v.num_buckets * v.bucket_size) {
table_with_length::add(&mut v.buckets, v.num_buckets, vector::empty());
v.num_buckets = v.num_buckets + 1;
};
- vector::push_back(table_with_length::borrow_mut(&mut v.buckets, v.end_index.bucket_index), val);
- increment_index(&mut v.end_index, v.bucket_size);
- }
-
- /// Add element `val` to the end of the vector `v`.
- /// Aborts if all buckets are full.
- /// It can split the gas responsibility between user of the vector and owner of the vector.
- /// Call `reserve` to explicit add more buckets.
- public fun push_back_no_grow(v: &mut BigVector, val: T) {
- assert!(v.end_index.bucket_index < v.num_buckets, error::invalid_argument(EOUT_OF_CAPACITY));
- vector::push_back(table_with_length::borrow_mut(&mut v.buckets, v.end_index.bucket_index), val);
- increment_index(&mut v.end_index, v.bucket_size);
+ vector::push_back(table_with_length::borrow_mut(&mut v.buckets, v.num_buckets - 1), val);
+ v.end_index = v.end_index + 1;
}
/// Pop an element from the end of vector `v`. It doesn't shrink the buckets even if they're empty.
@@ -82,140 +95,138 @@ module aptos_std::big_vector {
/// Aborts if `v` is empty.
public fun pop_back(v: &mut BigVector): T {
assert!(!is_empty(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
- decrement_index(&mut v.end_index, v.bucket_size);
- let val = vector::pop_back(table_with_length::borrow_mut(&mut v.buckets, v.end_index.bucket_index));
+ let last_bucket = table_with_length::borrow_mut(&mut v.buckets, v.num_buckets - 1);
+ let val = vector::pop_back(last_bucket);
+ // Shrink the table if the last vector is empty.
+ if (vector::is_empty(last_bucket)) {
+ move last_bucket;
+ vector::destroy_empty(table_with_length::remove(&mut v.buckets, v.num_buckets - 1));
+ v.num_buckets = v.num_buckets - 1;
+ };
+ v.end_index = v.end_index - 1;
val
}
- /// Acquire an immutable reference to the `i`th element of the vector `v`.
- /// Aborts if `i` is out of bounds.
- public fun borrow(v: &BigVector, index: &BigVectorIndex): &T {
- vector::borrow(table_with_length::borrow(&v.buckets, index.bucket_index), index.vec_index)
- }
-
- /// Return a mutable reference to the `i`th element in the vector `v`.
- /// Aborts if `i` is out of bounds.
- public fun borrow_mut(v: &mut BigVector, index: &BigVectorIndex): &mut T {
- vector::borrow_mut(table_with_length::borrow_mut(&mut v.buckets, index.bucket_index), index.vec_index)
- }
-
- /// Return the length of the vector.
- public fun length(v: &BigVector): u64 {
- v.end_index.bucket_index * v.bucket_size + v.end_index.vec_index
- }
-
- /// Return `true` if the vector `v` has no elements and `false` otherwise.
- public fun is_empty(v: &BigVector): bool {
- length(v) == 0
+ /// Remove the element at index i in the vector v and return the owned value that was previously stored at i in v.
+ /// 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(v: &mut BigVector, i: u64): T {
+ let len = length(v);
+ assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ while (i + 1 < len) {
+ swap(v, i, i + 1);
+ i = i + 1;
+ };
+ pop_back(v)
}
/// Swap the `i`th element of the vector `v` 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(v: &mut BigVector, index: &BigVectorIndex): T {
+ public fun swap_remove(v: &mut BigVector, i: u64): T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
let last_val = pop_back(v);
// if the requested value is the last one, return it
- if (v.end_index.bucket_index == index.bucket_index && v.end_index.vec_index == index.vec_index) {
+ if (v.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 = table_with_length::borrow_mut(&mut v.buckets, index.bucket_index);
+ let bucket = table_with_length::borrow_mut(&mut v.buckets, i / v.bucket_size);
let bucket_len = vector::length(bucket);
- let val = vector::swap_remove(bucket, index.vec_index);
+ let val = vector::swap_remove(bucket, i % v.bucket_size);
vector::push_back(bucket, last_val);
- vector::swap(bucket, index.vec_index, bucket_len - 1);
+ vector::swap(bucket, i % v.bucket_size, bucket_len - 1);
val
}
- /// Return true if `val` is in the vector `v`.
- public fun contains(v: &BigVector, val: &T): bool {
- if (is_empty(v)) return false;
- let (exist, _) = index_of(v, val);
- exist
+ /// Swap the elements at the i'th and j'th indices in the vector v. Will abort if either of i or j are out of bounds
+ /// for v.
+ public fun swap(v: &mut BigVector, i: u64, j: u64) {
+ assert!(i < length(v) && j < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let i_bucket_index = i / v.bucket_size;
+ let j_bucket_index = j / v.bucket_size;
+ let i_vector_index = i % v.bucket_size;
+ let j_vector_index = j % v.bucket_size;
+ if (i_bucket_index == j_bucket_index) {
+ vector::swap(table_with_length::borrow_mut(&mut v.buckets, i_bucket_index), 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 = table_with_length::remove(&mut v.buckets, i_bucket_index);
+ let bucket_j = table_with_length::remove(&mut v.buckets, j_bucket_index);
+ // Get the elements from buckets by calling `swap_remove`.
+ let element_i = vector::swap_remove(&mut bucket_i, i_vector_index);
+ let element_j = vector::swap_remove(&mut bucket_j, j_vector_index);
+ // Swap the elements and push back to the other bucket.
+ vector::push_back(&mut bucket_i, element_j);
+ vector::push_back(&mut bucket_j, element_i);
+ let last_index_in_bucket_i = vector::length(&bucket_i) - 1;
+ let last_index_in_bucket_j = vector::length(&bucket_j) - 1;
+ // Re-position the swapped elements to the right index.
+ vector::swap(&mut bucket_i, i_vector_index, last_index_in_bucket_i);
+ vector::swap(&mut bucket_j, j_vector_index, last_index_in_bucket_j);
+ // Add back the buckets.
+ table_with_length::add(&mut v.buckets, i_bucket_index, bucket_i);
+ table_with_length::add(&mut v.buckets, j_bucket_index, bucket_j);
}
- /// Return `(true, i)` if `val` is in the vector `v` at index `i`.
- /// Otherwise, returns `(false, 0)`.
+ /// Reverse the order of the elements in the vector v in-place.
+ /// disclaimer: this function is costly. Use it at your own discretion.
+ public fun reverse(v: &mut BigVector) {
+ let len = length(v);
+ let half_len = len / 2;
+ let k = 0;
+ while (k < half_len) {
+ swap(v, k, len - 1 - k);
+ k = k + 1;
+ }
+ }
+
+ /// Return the index of the first occurrence of an element in v 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(v: &BigVector, val: &T): (bool, u64) {
let i = 0;
let len = length(v);
- let index = bucket_index(v, 0);
while (i < len) {
- if (borrow(v, &index) == val) {
+ if (borrow(v, i) == val) {
return (true, i)
};
i = i + 1;
- increment_index(&mut index, v.bucket_size);
};
(false, 0)
}
- /// Buckets related API
-
- /// Return corresponding BigVectorIndex for `i`, we can avoid this once table supports lookup by value instead of by reference.
- /// Aborts if `i` is out of bounds.
- public fun bucket_index(v: &BigVector, i: u64): BigVectorIndex {
- assert!(i < length(v), EINDEX_OUT_OF_BOUNDS);
- BigVectorIndex {
- bucket_index: i / v.bucket_size,
- vec_index: i % v.bucket_size,
- }
- }
-
- /// Return the bucket size of the vector.
- public fun bucket_size(v: &BigVector): u64 {
- v.bucket_size
- }
-
- /// Equivalent to i = i + 1 for BigVectorIndex with `bucket_size`.
- public fun increment_index(index: &mut BigVectorIndex, bucket_size: u64) {
- if (index.vec_index + 1 == bucket_size) {
- index.bucket_index = index.bucket_index + 1;
- index.vec_index = 0;
- } else {
- index.vec_index = index.vec_index + 1;
- }
- }
-
- /// Equivalent to i = i - 1 for BigVectorIndex with `bucket_size`.
- /// Aborts if `i` becomes out of bounds.
- public fun decrement_index(index: &mut BigVectorIndex, bucket_size: u64) {
- if (index.vec_index == 0) {
- assert!(index.bucket_index > 0, EINDEX_OUT_OF_BOUNDS);
- index.bucket_index = index.bucket_index - 1;
- index.vec_index = bucket_size - 1;
- } else {
- index.vec_index = index.vec_index - 1;
- }
+ /// Return if an element equal to e exists in the vector v.
+ /// disclaimer: this function is costly. Use it at your own discretion.
+ public fun contains(v: &BigVector, val: &T): bool {
+ if (is_empty(v)) return false;
+ let (exist, _) = index_of(v, val);
+ exist
}
- /// Reserve `additional_buckets` more buckets.
- public fun reserve(v: &mut BigVector, additional_buckets: u64) {
- while (additional_buckets > 0) {
- table_with_length::add(&mut v.buckets, v.num_buckets, vector::empty());
- v.num_buckets = v.num_buckets + 1;
- additional_buckets = additional_buckets - 1;
- }
+ /// Return the length of the vector.
+ public fun length(v: &BigVector): u64 {
+ v.end_index
}
- /// Shrink the buckets to fit the current length.
- public fun shrink_to_fit(v: &mut BigVector) {
- while (v.num_buckets > buckets_required(&v.end_index)) {
- v.num_buckets = v.num_buckets - 1;
- let v = table_with_length::remove(&mut v.buckets, v.num_buckets);
- vector::destroy_empty(v);
- }
+ /// Return `true` if the vector `v` has no elements and `false` otherwise.
+ public fun is_empty(v: &BigVector): bool {
+ length(v) == 0
}
- fun buckets_required(end_index: &BigVectorIndex): u64 {
- let additional = if (end_index.vec_index == 0) { 0 } else { 1 };
- end_index.bucket_index + additional
+ #[test_only]
+ fun destroy(v: BigVector) {
+ while (!is_empty(&mut v)) {
+ pop_back(&mut v);
+ };
+ destroy_empty(v)
}
#[test]
fun big_vector_test() {
- let v = new(5);
+ let v = empty(5);
let i = 0;
while (i < 100) {
push_back(&mut v, i);
@@ -223,8 +234,7 @@ module aptos_std::big_vector {
};
let j = 0;
while (j < 100) {
- let index = bucket_index(&v, j);
- let val = borrow(&v, &index);
+ let val = borrow(&v, j);
assert!(*val == j, 0);
j = j + 1;
};
@@ -240,71 +250,126 @@ module aptos_std::big_vector {
push_back(&mut v, i);
i = i + 1;
};
- let last_index = bucket_index(&v, length(&v) - 1);
- assert!(swap_remove(&mut v, &last_index) == 99, 0);
- let first_index = bucket_index(&v, 0);
- assert!(swap_remove(&mut v, &first_index) == 0, 0);
+ let last_index = length(&v) - 1;
+ assert!(swap_remove(&mut v, last_index) == 99, 0);
+ assert!(swap_remove(&mut v, 0) == 0, 0);
while (length(&v) > 0) {
// the vector is always [N, 1, 2, ... N-1] with repetitive swap_remove(&mut v, 0)
let expected = length(&v);
- let index = bucket_index(&v, 0);
- let val = swap_remove(&mut v, &index);
+ let val = swap_remove(&mut v, 0);
assert!(val == expected, 0);
};
- shrink_to_fit(&mut v);
destroy_empty(v);
}
#[test]
- #[expected_failure]
- fun big_vector_need_grow() {
- let v = new_with_capacity(5, 1);
+ fun big_vector_append_edge_case_test() {
+ let v1 = empty(5);
+ let v2 = singleton(1u64, 7);
+ let v3 = empty(6);
+ let v4 = empty(8);
+ append(&mut v3, v4);
+ assert!(length(&v3) == 0, 0);
+ append(&mut v2, v3);
+ assert!(length(&v2) == 1, 0);
+ append(&mut v1, v2);
+ assert!(length(&v1) == 1, 0);
+ destroy(v1);
+ }
+
+ #[test]
+ fun big_vector_append_test() {
+ let v1 = empty(5);
+ let v2 = empty(7);
let i = 0;
- while (i < 6) {
- push_back_no_grow(&mut v, i);
+ while (i < 7) {
+ push_back(&mut v1, i);
i = i + 1;
};
- destroy_empty(v);
+ while (i < 25) {
+ push_back(&mut v2, i);
+ i = i + 1;
+ };
+ append(&mut v1, v2);
+ assert!(length(&v1) == 25, 0);
+ i = 0;
+ while (i < 25) {
+ assert!(*borrow(&v1, i) == i, 0);
+ i = i + 1;
+ };
+ destroy(v1);
}
#[test]
- fun big_vector_reserve_and_shrink() {
- let v = new (10);
- reserve(&mut v, 10);
- assert!(v.num_buckets == 10, 0);
+ fun big_vector_remove_and_reverse_test() {
+ let v = empty(11);
let i = 0;
- while (i < 100) {
- push_back_no_grow(&mut v, i);
+ while (i < 101) {
+ push_back(&mut v, i);
i = i + 1;
};
- while (i < 120) {
+ remove(&mut v, 100);
+ remove(&mut v, 90);
+ remove(&mut v, 80);
+ remove(&mut v, 70);
+ remove(&mut v, 60);
+ remove(&mut v, 50);
+ remove(&mut v, 40);
+ remove(&mut v, 30);
+ remove(&mut v, 20);
+ remove(&mut v, 10);
+ remove(&mut v, 0);
+ assert!(length(&v) == 90, 0);
+
+ let index = 0;
+ i = 0;
+ while (i < 101) {
+ if (i % 10 != 0) {
+ assert!(*borrow(&v, index) == i, 0);
+ index = index + 1;
+ };
+ i = i + 1;
+ };
+ destroy(v);
+ }
+
+ #[test]
+ fun big_vector_swap_test() {
+ let v = empty(11);
+ let i = 0;
+ while (i < 101) {
push_back(&mut v, i);
i = i + 1;
};
- while (i > 90) {
- pop_back(&mut v);
- i = i - 1;
+ i = 0;
+ while (i < 51) {
+ swap(&mut v, i, 100 - i);
+ i = i + 1;
};
- assert!(v.num_buckets == 12, 0);
- shrink_to_fit(&mut v);
- assert!(v.num_buckets == 9, 0);
- while (i > 55) {
- pop_back(&mut v);
- i = i - 1;
+ i = 0;
+ while (i < 101) {
+ assert!(*borrow(&v, i) == 100 - i, 0);
+ i = i + 1;
};
- shrink_to_fit(&mut v);
- assert!(v.num_buckets == 6, 0);
- while (i > 0) {
- pop_back(&mut v);
- i = i - 1;
+ destroy(v);
+ }
+
+ #[test]
+ fun big_vector_index_of_test() {
+ let v = empty(11);
+ let i = 0;
+ while (i < 100) {
+ push_back(&mut v, i);
+ let (found, idx) = index_of(&mut v, &i);
+ assert!(found && idx == i, 0);
+ i = i + 1;
};
- shrink_to_fit(&mut v);
- destroy_empty(v);
+ destroy(v);
}
#[test]
fun big_vector_empty_contains() {
- let v = new (10);
+ let v = empty (10);
assert!(!contains(&v, &(1 as u64)), 0);
destroy_empty(v);
}
diff --git a/aptos-move/move-examples/data_structures/sources/smart_vector.move b/aptos-move/move-examples/data_structures/sources/smart_vector.move
new file mode 100644
index 0000000000000..06a76d53a8c60
--- /dev/null
+++ b/aptos-move/move-examples/data_structures/sources/smart_vector.move
@@ -0,0 +1,461 @@
+module aptos_framework::smart_vector {
+ use std::error;
+ use std::vector;
+ use aptos_std::big_vector::{Self, BigVector};
+ use aptos_std::math64::max;
+ use aptos_std::type_info::size_of_val;
+ use std::option::{Self, Option};
+
+ /// Vector index is out of bounds
+ const EINDEX_OUT_OF_BOUNDS: u64 = 1;
+ /// Cannot destroy a non-empty vector
+ const EVECTOR_NOT_EMPTY: u64 = 2;
+ /// Bucket size cannot be 0
+ const EZERO_BUCKET_SIZE: u64 = 3;
+
+ /// A Scalable vector implementation based on tables, elements are grouped into buckets with `bucket_size`.
+ /// The vector wrapping BigVector represents an option w/o `Drop` ability requirement of T to save space of the
+ /// metadata associated with BigVector when smart_vector is so small that inline_vec vector can hold all the data.
+ struct SmartVector has store {
+ inline_vec: vector,
+ big_vec: vector>,
+ inline_capacity: Option,
+ bucket_size: Option,
+ }
+
+ /// Regular Vector API
+
+ /// Create an empty vector using default logic to estimate `inline_capacity` and `bucket_size`, which may be
+ /// inaccurate.
+ public fun empty(): SmartVector {
+ SmartVector {
+ inline_vec: vector[],
+ big_vec: vector[],
+ inline_capacity: option::none(),
+ bucket_size: option::none(),
+ }
+ }
+
+ /// Create an empty vector with customized config.
+ /// When inline_capacity = 0, SmartVector degrades to a wrapper of BigVector.
+ public fun empty_with_config(inline_capacity: u64, bucket_size: u64): SmartVector {
+ assert!(bucket_size > 0, error::invalid_argument(EZERO_BUCKET_SIZE));
+ SmartVector {
+ inline_vec: vector[],
+ big_vec: vector[],
+ inline_capacity: option::some(inline_capacity),
+ bucket_size: option::some(bucket_size),
+ }
+ }
+
+ /// Create a vector of length 1 containing the passed in element.
+ public fun singleton(e: T): SmartVector {
+ let v = empty();
+ push_back(&mut v, e);
+ v
+ }
+
+ /// Destroy the vector `v`.
+ /// Aborts if `v` is not empty.
+ public fun destroy_empty(v: SmartVector) {
+ assert!(is_empty(&v), error::invalid_argument(EVECTOR_NOT_EMPTY));
+ let SmartVector { inline_vec, big_vec, inline_capacity: _, bucket_size: _} = v;
+ vector::destroy_empty(inline_vec);
+ vector::destroy_empty(big_vec);
+ }
+
+ /// Acquire an immutable reference to the `i`th element of the vector `v`.
+ /// Aborts if `i` is out of bounds.
+ public fun borrow(v: &SmartVector, i: u64): &T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ if (i < inline_len) {
+ vector::borrow(&v.inline_vec, i)
+ } else {
+ big_vector::borrow(vector::borrow(&v.big_vec, 0), i - inline_len)
+ }
+ }
+
+ /// Return a mutable reference to the `i`th element in the vector `v`.
+ /// Aborts if `i` is out of bounds.
+ public fun borrow_mut(v: &mut SmartVector, i: u64): &mut T {
+ assert!(i < length(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ if (i < inline_len) {
+ vector::borrow_mut(&mut v.inline_vec, i)
+ } else {
+ big_vector::borrow_mut(vector::borrow_mut(&mut v.big_vec, 0), i - inline_len)
+ }
+ }
+
+ /// Empty and destroy the other vector, and push each of the elements in the other vector onto the lhs vector in the
+ /// same order as they occurred in other.
+ /// disclaimer: this function may be costly. Use it at your own discretion.
+ public fun append(lhs: &mut SmartVector, other: SmartVector) {
+ let other_len = length(&other);
+ let half_other_len = other_len / 2;
+ let i = 0;
+ while (i < half_other_len) {
+ push_back(lhs, swap_remove(&mut other, i));
+ i = i + 1;
+ };
+ while (i < other_len) {
+ push_back(lhs, pop_back(&mut other));
+ i = i + 1;
+ };
+ destroy_empty(other);
+ }
+
+ /// Add element `val` to the end of the vector `v`. 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(v: &mut SmartVector, val: T) {
+ let len = length(v);
+ let inline_len = vector::length(&v.inline_vec);
+ if (len == inline_len) {
+ let bucket_size = if (option::is_some(&v.inline_capacity)) {
+ if (len < *option::borrow(&v.inline_capacity)) {
+ vector::push_back(&mut v.inline_vec, val);
+ return
+ };
+ *option::borrow(&v.bucket_size)
+ } else {
+ let val_size = size_of_val(&val);
+ if (val_size * (inline_len + 1) < 150 /* magic number */) {
+ vector::push_back(&mut v.inline_vec, val);
+ return
+ };
+ let estimated_avg_size = max((size_of_val(&v.inline_vec) + val_size) / (inline_len + 1), 1);
+ max(1024 /* free_write_quota */ / estimated_avg_size, 1)
+ };
+ vector::push_back(&mut v.big_vec, big_vector::empty(bucket_size));
+ };
+ big_vector::push_back(vector::borrow_mut(&mut v.big_vec, 0), val);
+ }
+
+ /// Pop an element from the end of vector `v`. It does shrink the buckets if they're empty.
+ /// Aborts if `v` is empty.
+ public fun pop_back(v: &mut SmartVector): T {
+ assert!(!is_empty(v), error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let big_vec_wrapper = &mut v.big_vec;
+ if (vector::length(big_vec_wrapper) != 0) {
+ let big_vec = vector::pop_back(big_vec_wrapper);
+ let val = big_vector::pop_back(&mut big_vec);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ vector::push_back(big_vec_wrapper, big_vec);
+ };
+ val
+ } else {
+ vector::pop_back(&mut v.inline_vec)
+ }
+ }
+
+ /// Remove the element at index i in the vector v and return the owned value that was previously stored at i in v.
+ /// 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 may be costly. Use it at your own discretion.
+ public fun remove(v: &mut SmartVector, i: u64): T {
+ let len = length(v);
+ assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ if (i < inline_len) {
+ vector::remove(&mut v.inline_vec, i)
+ } else {
+ big_vector::remove(vector::borrow_mut(&mut v.big_vec, 0), i - inline_len)
+ }
+ }
+
+ /// Swap the `i`th element of the vector `v` 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(v: &mut SmartVector, i: u64): T {
+ let len = length(v);
+ assert!(i < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ let big_vec_wrapper = &mut v.big_vec;
+ let inline_vec = &mut v.inline_vec;
+ if (i >= inline_len) {
+ let big_vec = vector::pop_back(big_vec_wrapper);
+ let val = big_vector::swap_remove(&mut big_vec, i - inline_len);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ vector::push_back(big_vec_wrapper, big_vec);
+ };
+ val
+ } else {
+ if (inline_len < len) {
+ let big_vec = vector::pop_back(big_vec_wrapper);
+ let last_from_big_vec = big_vector::pop_back(&mut big_vec);
+ if (big_vector::is_empty(&big_vec)) {
+ big_vector::destroy_empty(big_vec)
+ } else {
+ vector::push_back(big_vec_wrapper, big_vec);
+ };
+ vector::push_back(inline_vec, last_from_big_vec);
+ };
+ vector::swap_remove(inline_vec, i)
+ }
+ }
+
+ /// Swap the elements at the i'th and j'th indices in the vector v. Will abort if either of i or j are out of bounds
+ /// for v.
+ public fun swap(v: &mut SmartVector, i: u64, j: u64) {
+ if (i > j) {
+ return swap(v, j, i)
+ };
+ let len = length(v);
+ assert!(j < len, error::invalid_argument(EINDEX_OUT_OF_BOUNDS));
+ let inline_len = vector::length(&v.inline_vec);
+ if (i >= inline_len) {
+ big_vector::swap(vector::borrow_mut(&mut v.big_vec, 0), i - inline_len, j - inline_len);
+ } else if (j < inline_len) {
+ vector::swap(&mut v.inline_vec, i, j);
+ } else {
+ let big_vec = vector::borrow_mut(&mut v.big_vec, 0);
+ let inline_vec = &mut v.inline_vec;
+ let element_i = vector::swap_remove(inline_vec, i);
+ let element_j = big_vector::swap_remove(big_vec, j - inline_len);
+ vector::push_back(inline_vec, element_j);
+ vector::swap(inline_vec, i, inline_len - 1);
+ big_vector::push_back(big_vec, element_i);
+ big_vector::swap(big_vec, j - inline_len, len - inline_len - 1);
+ }
+ }
+
+ /// Reverse the order of the elements in the vector v in-place.
+ /// disclaimer: this function may be costly. Use it at your own discretion.
+ public fun reverse(v: &mut SmartVector) {
+ let i = 0;
+ let len = length(v);
+ let half_len = len / 2;
+ let k = 0;
+ while (k < half_len) {
+ swap(v, i + k, len - 1 - k);
+ k = k + 1;
+ }
+ }
+
+ /// Return `(true, i)` if `val` is in the vector `v` at index `i`.
+ /// Otherwise, returns `(false, 0)`.
+ /// disclaimer: this function may be costly. Use it at your own discretion.
+ public fun index_of(v: &SmartVector, val: &T): (bool, u64) {
+ let i = 0;
+ let len = length(v);
+ while (i < len) {
+ if (borrow(v, i) == val) {
+ return (true, i)
+ };
+ i = i + 1;
+ };
+ (false, 0)
+ }
+
+ /// Return true if `val` is in the vector `v`.
+ /// disclaimer: this function may be costly. Use it at your own discretion.
+ public fun contains(v: &SmartVector, val: &T): bool {
+ if (is_empty(v)) return false;
+ let (exist, _) = index_of(v, val);
+ exist
+ }
+
+ /// Return the length of the vector.
+ public fun length(v: &SmartVector): u64 {
+ vector::length(&v.inline_vec) + if (vector::is_empty(&v.big_vec)) {
+ 0
+ } else {
+ big_vector::length(vector::borrow(&v.big_vec, 0))
+ }
+ }
+
+ /// Return `true` if the vector `v` has no elements and `false` otherwise.
+ public fun is_empty(v: &SmartVector): bool {
+ length(v) == 0
+ }
+
+ #[test_only]
+ fun destroy(v: SmartVector) {
+ while (!is_empty(&mut v)) {
+ let _ = pop_back(&mut v);
+ };
+ destroy_empty(v)
+ }
+
+ #[test]
+ fun smart_vector_test() {
+ let v = empty();
+ let i = 0;
+ while (i < 100) {
+ push_back(&mut v, i);
+ i = i + 1;
+ };
+ let j = 0;
+ while (j < 100) {
+ let val = borrow(&v, j);
+ assert!(*val == j, 0);
+ j = j + 1;
+ };
+ while (i > 0) {
+ i = i - 1;
+ let (exist, index) = index_of(&v, &i);
+ let j = pop_back(&mut v);
+ assert!(exist, 0);
+ assert!(index == i, 0);
+ assert!(j == i, 0);
+ };
+ while (i < 100) {
+ push_back(&mut v, i);
+ i = i + 1;
+ };
+ let last_index = length(&v) - 1;
+ assert!(swap_remove(&mut v, last_index) == 99, 0);
+ assert!(swap_remove(&mut v, 0) == 0, 0);
+ while (length(&v) > 0) {
+ // the vector is always [N, 1, 2, ... N-1] with repetitive swap_remove(&mut v, 0)
+ let expected = length(&v);
+ let val = swap_remove(&mut v, 0);
+ assert!(val == expected, 0);
+ };
+ destroy_empty(v);
+ }
+
+ #[test]
+ fun smart_vector_append_edge_case_test() {
+ let v1 = empty();
+ let v2 = singleton(1u64);
+ let v3 = empty();
+ let v4 = empty();
+ append(&mut v3, v4);
+ assert!(length(&v3) == 0, 0);
+ append(&mut v2, v3);
+ assert!(length(&v2) == 1, 0);
+ append(&mut v1, v2);
+ assert!(length(&v1) == 1, 0);
+ destroy(v1);
+ }
+
+ #[test]
+ fun smart_vector_append_test() {
+ let v1 = empty();
+ let v2 = empty();
+ let i = 0;
+ while (i < 7) {
+ push_back(&mut v1, i);
+ i = i + 1;
+ };
+ while (i < 25) {
+ push_back(&mut v2, i);
+ i = i + 1;
+ };
+ append(&mut v1, v2);
+ assert!(length(&v1) == 25, 0);
+ i = 0;
+ while (i < 25) {
+ assert!(*borrow(&v1, i) == i, 0);
+ i = i + 1;
+ };
+ destroy(v1);
+ }
+
+ #[test]
+ fun smart_vector_remove_test() {
+ let v = empty();
+ let i = 0u64;
+ while (i < 101) {
+ push_back(&mut v, i);
+ i = i + 1;
+ };
+ let inline_len = vector::length(&v.inline_vec);
+ remove(&mut v, 100);
+ remove(&mut v, 90);
+ remove(&mut v, 80);
+ remove(&mut v, 70);
+ remove(&mut v, 60);
+ remove(&mut v, 50);
+ remove(&mut v, 40);
+ remove(&mut v, 30);
+ remove(&mut v, 20);
+ assert!(vector::length(&v.inline_vec) == inline_len, 0);
+ remove(&mut v, 10);
+ assert!(vector::length(&v.inline_vec) + 1 == inline_len, 0);
+ remove(&mut v, 0);
+ assert!(vector::length(&v.inline_vec) + 2 == inline_len, 0);
+ assert!(length(&v) == 90, 0);
+
+ let index = 0;
+ i = 0;
+ while (i < 101) {
+ if (i % 10 != 0) {
+ assert!(*borrow(&v, index) == i, 0);
+ index = index + 1;
+ };
+ i = i + 1;
+ };
+ destroy(v);
+ }
+
+ #[test]
+ fun smart_vector_reverse_test() {
+ let v = empty();
+ let i = 0u64;
+ while (i < 10) {
+ push_back(&mut v, i);
+ i = i + 1;
+ };
+ reverse(&mut v);
+ let k = 0;
+ while (k < 10) {
+ assert!(*vector::borrow(&v.inline_vec, k) == 9 - k, 0);
+ k = k + 1;
+ };
+ while (i < 100) {
+ push_back(&mut v, i);
+ i = i + 1;
+ };
+ while (!vector::is_empty(&v.inline_vec)) {
+ remove(&mut v, 0);
+ };
+ reverse(&mut v);
+ i = 0;
+ let len = length(&v);
+ while (i + 1 < len) {
+ assert!(*big_vector::borrow(vector::borrow(&v.big_vec, 0), i) == *big_vector::borrow(vector::borrow(&v.big_vec, 0), i + 1) + 1, 0);
+ i = i + 1;
+ };
+ destroy(v);
+ }
+
+ #[test]
+ fun smart_vector_swap_test() {
+ let v = empty();
+ let i = 0;
+ while (i < 101) {
+ push_back(&mut v, i);
+ i = i + 1;
+ };
+ i = 0;
+ while (i < 51) {
+ swap(&mut v, i, 100 - i);
+ i = i + 1;
+ };
+ i = 0;
+ while (i < 101) {
+ assert!(*borrow(&v, i) == 100 - i, 0);
+ i = i + 1;
+ };
+ destroy(v);
+ }
+
+ #[test]
+ fun smart_vector_index_of_test() {
+ let v = empty();
+ let i = 0;
+ while (i < 100) {
+ push_back(&mut v, i);
+ let (found, idx) = index_of(&mut v, &i);
+ assert!(found && idx == i, 0);
+ i = i + 1;
+ };
+ destroy(v);
+ }
+}