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 ith 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 ith 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 ith 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 ith 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 ith 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 ith 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); + } +}