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..0b0b9a8449494 100644 --- a/aptos-move/move-examples/data_structures/sources/big_vector.move +++ b/aptos-move/move-examples/data_structures/sources/big_vector.move @@ -5,45 +5,37 @@ 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; + /// Cannot pop back from an empty vector + const EVECTOR_EMPTY: u64 = 3; + /// bucket_size cannot be 0 + const EZERO_BUCKET_SIZE: u64 = 4; - /// 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, - num_buckets: u64, + end_index: u64, bucket_size: u64 } /// 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, - }, - num_buckets: 0, + end_index: 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(element: T, bucket_size: u64): BigVector { + let v = empty(bucket_size); + push_back(&mut v, element); v } @@ -51,171 +43,192 @@ 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; + let BigVector { buckets, end_index: _, bucket_size: _ } = v; table_with_length::destroy_empty(buckets); } - /// 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) { - table_with_length::add(&mut v.buckets, v.num_buckets, vector::empty()); - v.num_buckets = v.num_buckets + 1; + /// 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; }; - 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); + 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`. - /// 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); + /// 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) { + let num_buckets = table_with_length::length(&v.buckets); + if (v.end_index == num_buckets * v.bucket_size) { + table_with_length::add(&mut v.buckets, num_buckets, vector::empty()); + vector::push_back(table_with_length::borrow_mut(&mut v.buckets, num_buckets), val); + } else { + vector::push_back(table_with_length::borrow_mut(&mut v.buckets, 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. /// Call `shrink_to_fit` explicity to deallocate empty buckets. /// 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)); + assert!(!is_empty(v), error::invalid_state(EVECTOR_EMPTY)); + let num_buckets = table_with_length::length(&v.buckets); + let last_bucket = table_with_length::borrow_mut(&mut v.buckets, 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, 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 +236,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 +252,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..b4f750624f61a --- /dev/null +++ b/aptos-move/move-examples/data_structures/sources/smart_vector.move @@ -0,0 +1,463 @@ +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; + /// Cannot pop back from an empty vector + const EVECTOR_EMPTY: u64 = 3; + /// bucket_size cannot be 0 + const EZERO_BUCKET_SIZE: u64 = 4; + + /// 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(element: T): SmartVector { + let v = empty(); + push_back(&mut v, element); + 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_state(EVECTOR_EMPTY)); + 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); + } +} diff --git a/aptos-move/move-examples/post_mint_reveal_nft/sources/minting.move b/aptos-move/move-examples/post_mint_reveal_nft/sources/minting.move index 8be869893b770..1bb4d0f5345d0 100644 --- a/aptos-move/move-examples/post_mint_reveal_nft/sources/minting.move +++ b/aptos-move/move-examples/post_mint_reveal_nft/sources/minting.move @@ -228,7 +228,7 @@ module post_mint_reveal_nft::minting { token_mutate_config: create_token_mutability_config(&token_mutate_config), royalty_points_den, royalty_points_num, - tokens: big_vector::new(128), + tokens: big_vector::empty(128), public_mint_limit, }); @@ -452,8 +452,7 @@ module post_mint_reveal_nft::minting { // Randomize which token we're assigning to the user. let index = now % big_vector::length(&collection_config.tokens); - let bucket_index = big_vector::bucket_index(&collection_config.tokens, index); - let token = big_vector::swap_remove(&mut collection_config.tokens, &bucket_index); + let token = big_vector::swap_remove(&mut collection_config.tokens, index); // The name of the destination token will be based on the property version of the source certificate token. let token_name = collection_config.token_name_base;