diff --git a/aptos-move/framework/aptos-framework/doc/big_ordered_map.md b/aptos-move/framework/aptos-framework/doc/big_ordered_map.md index 83415aaeb25cd3..935161481639a1 100644 --- a/aptos-move/framework/aptos-framework/doc/big_ordered_map.md +++ b/aptos-move/framework/aptos-framework/doc/big_ordered_map.md @@ -32,19 +32,34 @@ allowing cleaner iterator APIs. - [Enum `BigOrderedMap`](#0x1_big_ordered_map_BigOrderedMap) - [Constants](#@Constants_0) - [Function `new`](#0x1_big_ordered_map_new) +- [Function `new_with_type_size_hints`](#0x1_big_ordered_map_new_with_type_size_hints) - [Function `new_with_config`](#0x1_big_ordered_map_new_with_config) - [Function `new_from`](#0x1_big_ordered_map_new_from) - [Function `destroy_empty`](#0x1_big_ordered_map_destroy_empty) - [Function `allocate_spare_slots`](#0x1_big_ordered_map_allocate_spare_slots) +- [Function `is_empty`](#0x1_big_ordered_map_is_empty) +- [Function `compute_length`](#0x1_big_ordered_map_compute_length) - [Function `add`](#0x1_big_ordered_map_add) - [Function `upsert`](#0x1_big_ordered_map_upsert) - [Function `remove`](#0x1_big_ordered_map_remove) - [Function `add_all`](#0x1_big_ordered_map_add_all) +- [Function `pop_front`](#0x1_big_ordered_map_pop_front) +- [Function `pop_back`](#0x1_big_ordered_map_pop_back) - [Function `lower_bound`](#0x1_big_ordered_map_lower_bound) - [Function `find`](#0x1_big_ordered_map_find) - [Function `contains`](#0x1_big_ordered_map_contains) - [Function `borrow`](#0x1_big_ordered_map_borrow) - [Function `borrow_mut`](#0x1_big_ordered_map_borrow_mut) +- [Function `borrow_front`](#0x1_big_ordered_map_borrow_front) +- [Function `borrow_back`](#0x1_big_ordered_map_borrow_back) +- [Function `prev_key`](#0x1_big_ordered_map_prev_key) +- [Function `next_key`](#0x1_big_ordered_map_next_key) +- [Function `to_ordered_map`](#0x1_big_ordered_map_to_ordered_map) +- [Function `keys`](#0x1_big_ordered_map_keys) +- [Function `for_each_and_clear`](#0x1_big_ordered_map_for_each_and_clear) +- [Function `for_each`](#0x1_big_ordered_map_for_each) +- [Function `for_each_ref`](#0x1_big_ordered_map_for_each_ref) +- [Function `destroy`](#0x1_big_ordered_map_destroy) - [Function `new_begin_iter`](#0x1_big_ordered_map_new_begin_iter) - [Function `new_end_iter`](#0x1_big_ordered_map_new_end_iter) - [Function `iter_is_begin`](#0x1_big_ordered_map_iter_is_begin) @@ -54,9 +69,7 @@ allowing cleaner iterator APIs. - [Function `iter_borrow_mut`](#0x1_big_ordered_map_iter_borrow_mut) - [Function `iter_next`](#0x1_big_ordered_map_iter_next) - [Function `iter_prev`](#0x1_big_ordered_map_iter_prev) -- [Function `for_each`](#0x1_big_ordered_map_for_each) -- [Function `for_each_ref`](#0x1_big_ordered_map_for_each_ref) -- [Function `destroy`](#0x1_big_ordered_map_destroy) +- [Function `for_each_leaf_node_ref`](#0x1_big_ordered_map_for_each_leaf_node_ref) - [Function `borrow_node`](#0x1_big_ordered_map_borrow_node) - [Function `borrow_node_mut`](#0x1_big_ordered_map_borrow_node_mut) - [Function `add_or_upsert_impl`](#0x1_big_ordered_map_add_or_upsert_impl) @@ -77,13 +90,9 @@ allowing cleaner iterator APIs. - [Function `add_at`](#0x1_big_ordered_map_add_at) - [Function `update_key`](#0x1_big_ordered_map_update_key) - [Function `remove_at`](#0x1_big_ordered_map_remove_at) -- [Function `length`](#0x1_big_ordered_map_length) -- [Function `length_for_node`](#0x1_big_ordered_map_length_for_node) -- [Function `is_empty`](#0x1_big_ordered_map_is_empty) - [Specification](#@Specification_1) - [Function `add_at`](#@Specification_1_add_at) - [Function `remove_at`](#@Specification_1_remove_at) - - [Function `length_for_node`](#@Specification_1_length_for_node)
use 0x1::bcs;
@@ -425,24 +434,6 @@ Map key is not found
-
-
-
-
-const DEFAULT_INNER_MIN_DEGREE: u16 = 4;
-
-
-
-
-
-
-
-
-const DEFAULT_LEAF_MIN_DEGREE: u16 = 3;
-
-
-
-
@@ -493,6 +484,24 @@ Map isn't empty
+
+
+
+
+const INNER_MIN_DEGREE: u16 = 4;
+
+
+
+
+
+
+
+
+const LEAF_MIN_DEGREE: u16 = 3;
+
+
+
+
@@ -506,7 +515,7 @@ Map isn't empty
-const MAX_NODE_BYTES: u64 = 204800;
+const MAX_NODE_BYTES: u64 = 409600;
@@ -539,6 +548,7 @@ it is required to use new_with_config, to explicitly select automatic or specifi
public fun new<K: store, V: store>(): BigOrderedMap<K, V> {
+ // Use new_with_type_size_hints or new_with_config if your types have variable sizes.
assert!(
bcs::constant_serialized_size<K>().is_some() && bcs::constant_serialized_size<V>().is_some(),
error::invalid_argument(EINVALID_CONFIG_PARAMETER)
@@ -550,6 +560,49 @@ it is required to use new_with_config, to explicitly select automatic or specifi
+
+
+
+
+## Function `new_with_type_size_hints`
+
+Returns a new BigOrderedMap, configured based on passed key and value serialized size hints.
+
+
+public fun new_with_type_size_hints<K: store, V: store>(avg_key_bytes: u64, max_key_bytes: u64, avg_value_bytes: u64, max_value_bytes: u64): big_ordered_map::BigOrderedMap<K, V>
+
+
+
+
+
+Implementation
+
+
+public fun new_with_type_size_hints<K: store, V: store>(avg_key_bytes: u64, max_key_bytes: u64, avg_value_bytes: u64, max_value_bytes: u64): BigOrderedMap<K, V> {
+ assert!(avg_key_bytes <= max_key_bytes, error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+ assert!(avg_value_bytes <= max_value_bytes, error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+
+ let inner_max_degree_from_avg = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / avg_key_bytes), INNER_MIN_DEGREE as u64);
+ let inner_max_degree_from_max = MAX_NODE_BYTES / max_key_bytes;
+ assert!(inner_max_degree_from_max >= (INNER_MIN_DEGREE as u64), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+
+ let avg_entry_size = avg_key_bytes + avg_value_bytes;
+ let max_entry_size = max_key_bytes + max_value_bytes;
+
+ let leaf_max_degree_from_avg = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / avg_entry_size), LEAF_MIN_DEGREE as u64);
+ let leaf_max_degree_from_max = MAX_NODE_BYTES / max_entry_size;
+ assert!(leaf_max_degree_from_max >= (INNER_MIN_DEGREE as u64), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+
+ new_with_config(
+ min(inner_max_degree_from_avg, inner_max_degree_from_max) as u16,
+ min(leaf_max_degree_from_avg, leaf_max_degree_from_max) as u16,
+ false,
+ )
+}
+
+
+
+
@@ -565,6 +618,10 @@ Sizes of all elements must respect (or their additions will be rejected):
If keys or values have variable size, and first element could be non-representative in size (i.e. smaller than future ones),
it is important to compute and pass inner_max_degree and leaf_max_degree based on the largest element you want to be able to insert.
+reuse_slots
means that removing elements from the map doesn't free the storage slots and returns the refund.
+Together with allocate_spare_slots
, it allows to preallocate slots and have inserts have predictable gas costs.
+(otherwise, inserts that require map to add new nodes, cost significantly more, compared to the rest)
+
public fun new_with_config<K: store, V: store>(inner_max_degree: u16, leaf_max_degree: u16, reuse_slots: bool): big_ordered_map::BigOrderedMap<K, V>
@@ -576,8 +633,8 @@ it is important to compute and pass inner_max_degree and leaf_max_degree based o
public fun new_with_config<K: store, V: store>(inner_max_degree: u16, leaf_max_degree: u16, reuse_slots: bool): BigOrderedMap<K, V> {
- assert!(inner_max_degree == 0 || (inner_max_degree >= DEFAULT_INNER_MIN_DEGREE && (inner_max_degree as u64) <= MAX_DEGREE), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
- assert!(leaf_max_degree == 0 || (leaf_max_degree >= DEFAULT_LEAF_MIN_DEGREE && (leaf_max_degree as u64) <= MAX_DEGREE), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+ assert!(inner_max_degree == 0 || (inner_max_degree >= INNER_MIN_DEGREE && (inner_max_degree as u64) <= MAX_DEGREE), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+ assert!(leaf_max_degree == 0 || (leaf_max_degree >= LEAF_MIN_DEGREE && (leaf_max_degree as u64) <= MAX_DEGREE), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
// Assert that storage_slots_allocator special indices are aligned:
assert!(storage_slots_allocator::is_null_index(NULL_INDEX), error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
@@ -686,6 +743,62 @@ and better bounded/fair.
+
+
+
+
+## Function `is_empty`
+
+Returns true iff the BigOrderedMap is empty.
+
+
+public fun is_empty<K: store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>): bool
+
+
+
+
+
+Implementation
+
+
+public fun is_empty<K: store, V: store>(self: &BigOrderedMap<K, V>): bool {
+ let node = self.borrow_node(self.min_leaf_index);
+ node.children.is_empty()
+}
+
+
+
+
+
+
+
+
+## Function `compute_length`
+
+Returns the number of elements in the BigOrderedMap.
+This is an expensive function, as it goes through all the leaves to compute it.
+
+
+public fun compute_length<K: store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>): u64
+
+
+
+
+
+Implementation
+
+
+public fun compute_length<K: store, V: store>(self: &BigOrderedMap<K, V>): u64 {
+ let size = 0;
+ self.for_each_leaf_node_ref(|node| {
+ size += node.children.length();
+ });
+ size
+}
+
+
+
+
@@ -819,6 +932,60 @@ Aborts with EKEY_ALREADY_EXISTS if key already exist, or duplicate keys are pass
+
+
+
+
+## Function `pop_front`
+
+
+
+public fun pop_front<K: copy, drop, store, V: store>(self: &mut big_ordered_map::BigOrderedMap<K, V>): (K, V)
+
+
+
+
+
+Implementation
+
+
+public fun pop_front<K: drop + copy + store, V: store>(self: &mut BigOrderedMap<K, V>): (K, V) {
+ let it = self.new_begin_iter();
+ let k = *it.iter_borrow_key();
+ let v = self.remove(&k);
+ (k, v)
+}
+
+
+
+
+
+
+
+
+## Function `pop_back`
+
+
+
+public fun pop_back<K: copy, drop, store, V: store>(self: &mut big_ordered_map::BigOrderedMap<K, V>): (K, V)
+
+
+
+
+
+Implementation
+
+
+public fun pop_back<K: drop + copy + store, V: store>(self: &mut BigOrderedMap<K, V>): (K, V) {
+ let it = self.new_end_iter().iter_prev(self);
+ let k = *it.iter_borrow_key();
+ let v = self.remove(&k);
+ (k, v)
+}
+
+
+
+
@@ -946,12 +1113,7 @@ Returns a reference to the element with its key, aborts if the key is not found.
let iter = self.find(key);
assert!(!iter.iter_is_end(self), error::invalid_argument(EKEY_NOT_FOUND));
- // TODO cannot call iter_borrow, because reference checks assume return has reference to iter that is being destroyed
- // iter.iter_borrow(self)
-
- assert!(!iter.iter_is_end(self), error::invalid_argument(EITER_OUT_OF_BOUNDS));
- let children = &self.borrow_node(iter.node_index).children;
- &iter.child_iter.iter_borrow(children).value
+ iter.iter_borrow(self)
}
@@ -964,6 +1126,9 @@ Returns a reference to the element with its key, aborts if the key is not found.
## Function `borrow_mut`
Returns a mutable reference to the element with its key at the given index, aborts if the key is not found.
+Aborts with EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE if KV size doesn't have constant size,
+because if it doesn't we cannot assert invariants on the size.
+In case of variable size, use either borrow
, copy
then upsert
, or remove
and add
instead of mutable borrow.
public fun borrow_mut<K: copy, drop, store, V: store>(self: &mut big_ordered_map::BigOrderedMap<K, V>, key: &K): &mut V
@@ -984,6 +1149,303 @@ Returns a mutable reference to the element with its key at the given index, abor
+
+
+
+
+## Function `borrow_front`
+
+
+
+public fun borrow_front<K: copy, drop, store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>): (K, &V)
+
+
+
+
+
+Implementation
+
+
+public fun borrow_front<K: drop + copy + store, V: store>(self: &BigOrderedMap<K, V>): (K, &V) {
+ let it = self.new_begin_iter();
+ let key = *it.iter_borrow_key();
+ (key, it.iter_borrow(self))
+}
+
+
+
+
+
+
+
+
+## Function `borrow_back`
+
+
+
+public fun borrow_back<K: copy, drop, store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>): (K, &V)
+
+
+
+
+
+Implementation
+
+
+public fun borrow_back<K: drop + copy + store, V: store>(self: &BigOrderedMap<K, V>): (K, &V) {
+ let it = self.new_end_iter().iter_prev(self);
+ let key = *it.iter_borrow_key();
+ (key, it.iter_borrow(self))
+}
+
+
+
+
+
+
+
+
+## Function `prev_key`
+
+
+
+public fun prev_key<K: copy, drop, store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>, key: &K): option::Option<K>
+
+
+
+
+
+Implementation
+
+
+public fun prev_key<K: drop + copy + store, V: store>(self: &BigOrderedMap<K, V>, key: &K): Option<K> {
+ let it = self.lower_bound(key);
+ if (it.iter_is_begin(self)) {
+ option::none()
+ } else {
+ option::some(*it.iter_prev(self).iter_borrow_key())
+ }
+}
+
+
+
+
+
+
+
+
+## Function `next_key`
+
+
+
+public fun next_key<K: copy, drop, store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>, key: &K): option::Option<K>
+
+
+
+
+
+Implementation
+
+
+public fun next_key<K: drop + copy + store, V: store>(self: &BigOrderedMap<K, V>, key: &K): Option<K> {
+ let it = self.lower_bound(key);
+ if (it.iter_is_end(self)) {
+ option::none()
+ } else {
+ let cur_key = it.iter_borrow_key();
+ if (key == cur_key) {
+ let it = it.iter_next(self);
+ if (it.iter_is_end(self)) {
+ option::none()
+ } else {
+ option::some(*it.iter_borrow_key())
+ }
+ } else {
+ option::some(*cur_key)
+ }
+ }
+}
+
+
+
+
+
+
+
+
+## Function `to_ordered_map`
+
+Convert a BigOrderedMap to an OrderedMap, which is supposed to be called mostly by view functions to get an atomic
+view of the whole map.
+Disclaimer: This function may be costly as the BigOrderedMap may be huge in size. Use it at your own discretion.
+
+
+public fun to_ordered_map<K: copy, drop, store, V: copy, store>(self: &big_ordered_map::BigOrderedMap<K, V>): ordered_map::OrderedMap<K, V>
+
+
+
+
+
+Implementation
+
+
+public fun to_ordered_map<K: drop + copy + store, V: copy + store>(self: &BigOrderedMap<K, V>): OrderedMap<K, V> {
+ let result = ordered_map::new();
+ self.for_each_ref(|k, v| {
+ result.new_end_iter().iter_add(&mut result, *k, *v);
+ });
+ result
+}
+
+
+
+
+
+
+
+
+## Function `keys`
+
+Get all keys.
+
+For a large enough BigOrderedMap this function will fail due to execution gas limits,
+use iterartor or next_key/prev_key to iterate over across portion of the map.
+
+
+public fun keys<K: copy, drop, store, V: copy, store>(self: &big_ordered_map::BigOrderedMap<K, V>): vector<K>
+
+
+
+
+
+Implementation
+
+
+public fun keys<K: store + copy + drop, V: store + copy>(self: &BigOrderedMap<K, V>): vector<K> {
+ let result = vector[];
+ self.for_each_ref(|k, _v| {
+ result.push_back(*k);
+ });
+ result
+}
+
+
+
+
+
+
+
+
+## Function `for_each_and_clear`
+
+Apply the function to each element in the vector, consuming it, leaving the map empty.
+
+
+public fun for_each_and_clear<K: copy, drop, store, V: store>(self: &mut big_ordered_map::BigOrderedMap<K, V>, f: |(K, V)|)
+
+
+
+
+
+Implementation
+
+
+public inline fun for_each_and_clear<K: drop + copy + store, V: store>(self: &mut BigOrderedMap<K, V>, f: |K, V|) {
+ // TODO - this can be done more efficiently, by destroying the leaves directly
+ // but that requires more complicated code and testing.
+ while (!self.is_empty()) {
+ let (k, v) = self.pop_front();
+ f(k, v);
+ };
+}
+
+
+
+
+
+
+
+
+## Function `for_each`
+
+Apply the function to each element in the vector, consuming it, and consuming the map
+
+
+public fun for_each<K: copy, drop, store, V: store>(self: big_ordered_map::BigOrderedMap<K, V>, f: |(K, V)|)
+
+
+
+
+
+Implementation
+
+
+public inline fun for_each<K: drop + copy + store, V: store>(self: BigOrderedMap<K, V>, f: |K, V|) {
+ // TODO - this can be done more efficiently, by destroying the leaves directly
+ // but that requires more complicated code and testing.
+ self.for_each_and_clear(|k, v| f(k, v));
+ destroy_empty(self)
+}
+
+
+
+
+
+
+
+
+## Function `for_each_ref`
+
+Apply the function to a reference of each element in the vector.
+
+
+public fun for_each_ref<K: copy, drop, store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>, f: |(&K, &V)|)
+
+
+
+
+
+Implementation
+
+
+public inline fun for_each_ref<K: drop + copy + store, V: store>(self: &BigOrderedMap<K, V>, f: |&K, &V|) {
+ self.for_each_leaf_node_ref(|node| {
+ node.children.for_each_ref(|k: &K, v: &Child<V>| {
+ f(k, &v.value);
+ });
+ })
+}
+
+
+
+
+
+
+
+
+## Function `destroy`
+
+Destroy a map, by destroying elements individually.
+
+
+public fun destroy<K: copy, drop, store, V: store>(self: big_ordered_map::BigOrderedMap<K, V>, dv: |V|)
+
+
+
+
+
+Implementation
+
+
+public inline fun destroy<K: drop + copy + store, V: store>(self: BigOrderedMap<K, V>, dv: |V|) {
+ for_each(self, |_k, v| {
+ dv(v);
+ });
+}
+
+
+
+
@@ -1133,7 +1595,7 @@ Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end.
Note: Requires that the map is not changed after the input iterator is generated.
-public(friend) fun iter_borrow<K: store, V: store>(self: &big_ordered_map::IteratorPtr<K>, map: &big_ordered_map::BigOrderedMap<K, V>): &V
+public(friend) fun iter_borrow<K: drop, store, V: store>(self: big_ordered_map::IteratorPtr<K>, map: &big_ordered_map::BigOrderedMap<K, V>): &V
@@ -1142,10 +1604,11 @@ Note: Requires that the map is not changed after the input iterator is generated
Implementation
-public(friend) fun iter_borrow<K: store, V: store>(self: &IteratorPtr<K>, map: &BigOrderedMap<K, V>): &V {
+public(friend) fun iter_borrow<K: drop + store, V: store>(self: IteratorPtr<K>, map: &BigOrderedMap<K, V>): &V {
assert!(!self.iter_is_end(map), error::invalid_argument(EITER_OUT_OF_BOUNDS));
- let children = &map.borrow_node(self.node_index).children;
- &self.child_iter.iter_borrow(children).value
+ let IteratorPtr::Some { node_index, child_iter, key: _ } = self;
+ let children = &map.borrow_node(node_index).children;
+ &child_iter.iter_borrow(children).value
}
@@ -1160,11 +1623,13 @@ Note: Requires that the map is not changed after the input iterator is generated
Mutably borrows the value iterator points to.
Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end.
Aborts with EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE if KV size doesn't have constant size,
-because if it doesn't - we need to call upsert
to be able to check size invariants after modification.
+because if it doesn't we cannot assert invariants on the size.
+In case of variable size, use either borrow
, copy
then upsert
, or remove
and add
instead of mutable borrow.
+
Note: Requires that the map is not changed after the input iterator is generated.
-public(friend) fun iter_borrow_mut<K: store, V: store>(self: &big_ordered_map::IteratorPtr<K>, map: &mut big_ordered_map::BigOrderedMap<K, V>): &mut V
+public(friend) fun iter_borrow_mut<K: drop, store, V: store>(self: big_ordered_map::IteratorPtr<K>, map: &mut big_ordered_map::BigOrderedMap<K, V>): &mut V
@@ -1173,11 +1638,12 @@ Note: Requires that the map is not changed after the input iterator is generated
Implementation
-public(friend) fun iter_borrow_mut<K: store, V: store>(self: &IteratorPtr<K>, map: &mut BigOrderedMap<K, V>): &mut V {
+public(friend) fun iter_borrow_mut<K: drop + store, V: store>(self: IteratorPtr<K>, map: &mut BigOrderedMap<K, V>): &mut V {
assert!(map.constant_kv_size, error::invalid_argument(EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE));
assert!(!self.iter_is_end(map), error::invalid_argument(EITER_OUT_OF_BOUNDS));
- let children = &mut map.borrow_node_mut(self.node_index).children;
- &mut self.child_iter.iter_borrow_mut(children).value
+ let IteratorPtr::Some { node_index, child_iter, key: _ } = self;
+ let children = &mut map.borrow_node_mut(node_index).children;
+ &mut child_iter.iter_borrow_mut(children).value
}
@@ -1285,14 +1751,13 @@ Requires the map is not changed after the input iterator is generated.
-
+
-## Function `for_each`
+## Function `for_each_leaf_node_ref`
-Apply the function to each element in the vector, consuming it.
-public(friend) fun for_each<K: copy, drop, store, V: store>(self: big_ordered_map::BigOrderedMap<K, V>, f: |(K, V)|)
+fun for_each_leaf_node_ref<K: store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>, f: |&big_ordered_map::Node<K, V>|)
@@ -1301,73 +1766,14 @@ Apply the function to each element in the vector, consuming it.
Implementation
-public(friend) inline fun for_each<K: drop + copy + store, V: store>(self: BigOrderedMap<K, V>, f: |K, V|) {
- // TODO - this can be done more efficiently, by destroying the leaves directly
- // but that requires more complicated code and testing.
- let it = self.new_begin_iter();
- while (!it.iter_is_end(&self)) {
- let k = *it.iter_borrow_key();
- let v = self.remove(&k);
- f(k, v);
- it = self.new_begin_iter();
- };
- destroy_empty(self)
-}
-
-
-
+inline fun for_each_leaf_node_ref<K: store, V: store>(self: &BigOrderedMap<K, V>, f: |&Node<K, V>|) {
+ let cur_node_index = self.min_leaf_index;
-
-
-
-
-## Function `for_each_ref`
-
-Apply the function to a reference of each element in the vector.
-
-
-public(friend) fun for_each_ref<K: copy, drop, store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>, f: |(&K, &V)|)
-
-
-
-
-
-Implementation
-
-
-public(friend) inline fun for_each_ref<K: drop + copy + store, V: store>(self: &BigOrderedMap<K, V>, f: |&K, &V|) {
- let it = self.new_begin_iter();
- while (!it.iter_is_end(self)) {
- f(it.iter_borrow_key(), it.iter_borrow(self));
- it = it.iter_next(self);
- };
-}
-
-
-
-
-
-
-
-
-## Function `destroy`
-
-Destroy a map, by destroying elements individually.
-
-
-public(friend) fun destroy<K: copy, drop, store, V: store>(self: big_ordered_map::BigOrderedMap<K, V>, dv: |V|)
-
-
-
-
-
-Implementation
-
-
-public(friend) inline fun destroy<K: drop + copy + store, V: store>(self: BigOrderedMap<K, V>, dv: |V|) {
- for_each(self, |_k, v| {
- dv(v);
- });
+ while (cur_node_index != NULL_INDEX) {
+ let node = self.borrow_node(cur_node_index);
+ f(node);
+ cur_node_index = node.next;
+ }
}
@@ -1457,7 +1863,7 @@ Borrow a node mutably, given an index. Works for both root (i.e. inline) node an
// (optimizes out borrowing and path creation in `find_leaf_path`)
if (self.root.is_leaf) {
let children = &mut self.root.children;
- let degree = children.length();
+ let degree = children.length();
if (degree < (self.leaf_max_degree as u64)) {
let result = children.upsert(key, new_leaf_child(value));
@@ -1571,11 +1977,11 @@ Borrow a node mutably, given an index. Works for both root (i.e. inline) node an
let entry_size = key_size + value_size;
if (self.inner_max_degree == 0) {
- self.inner_max_degree = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / key_size), DEFAULT_INNER_MIN_DEGREE as u64) as u16;
+ self.inner_max_degree = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / key_size), INNER_MIN_DEGREE as u64) as u16;
};
if (self.leaf_max_degree == 0) {
- self.leaf_max_degree = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / entry_size), DEFAULT_LEAF_MIN_DEGREE as u64) as u16;
+ self.leaf_max_degree = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / entry_size), LEAF_MIN_DEGREE as u64) as u16;
};
// Make sure that no nodes can exceed the upper size limit.
@@ -1966,14 +2372,14 @@ If allow_overwrite
is not set, function will abort if keyfun add_at<K: drop + copy + store, V: store>(self: &mut BigOrderedMap<K, V>, path_to_node: vector<u64>, key: K, child: Child<V>, allow_overwrite: bool): Option<Child<V>> {
// Last node in the path is one where we need to add the child to.
- let node_index = path_to_node.pop_back();
+ let node_index = path_to_node.pop_back();
{
// First check if we can perform this operation, without changing structure of the tree (i.e. without adding any nodes).
// For that we can just borrow the single node
let node = self.borrow_node_mut(node_index);
let children = &mut node.children;
- let degree = children.length();
+ let degree = children.length();
// Compute directly, as we cannot use get_max_degree(), as self is already mutably borrowed.
let max_degree = if (node.is_leaf) {
@@ -2090,8 +2496,8 @@ If allow_overwrite
is not set, function will abort if keyadd(key, child);
let right_node_children = left_children.trim(target_size);
- assert!(left_children.length() <= max_degree, error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
- assert!(right_node_children.length() <= max_degree, error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
+ assert!(left_children.length() <= max_degree, error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
+ assert!(right_node_children.length() <= max_degree, error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
let right_node = new_node_with_children(is_leaf, right_node_children);
@@ -2149,7 +2555,7 @@ Given a path to node (excluding the node itself), which is currently stored unde
fun update_key<K: drop + copy + store, V: store>(self: &mut BigOrderedMap<K, V>, path_to_node: vector<u64>, old_key: &K, new_key: K) {
while (!path_to_node.is_empty()) {
- let node_index = path_to_node.pop_back();
+ let node_index = path_to_node.pop_back();
let node = self.borrow_node_mut(node_index);
let children = &mut node.children;
children.replace_key_inplace(old_key, new_key);
@@ -2183,7 +2589,7 @@ Given a path to node (excluding the node itself), which is currently stored unde
fun remove_at<K: drop + copy + store, V: store>(self: &mut BigOrderedMap<K, V>, path_to_node: vector<u64>, key: &K): Child<V> {
// Last node in the path is one where we need to remove the child from.
- let node_index = path_to_node.pop_back();
+ let node_index = path_to_node.pop_back();
let old_child = {
// First check if we can perform this operation, without changing structure of the tree (i.e. without rebalancing any nodes).
@@ -2200,7 +2606,7 @@ Given a path to node (excluding the node itself), which is currently stored unde
assert!(path_to_node.is_empty(), error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
- if (!is_leaf && children.length() == 1) {
+ if (!is_leaf && children.length() == 1) {
// If root is not leaf, but has a single child, promote only child to root,
// and drop current root. Since root is stored directly in the resource, we
// "move" the child into the root.
@@ -2226,7 +2632,7 @@ Given a path to node (excluding the node itself), which is currently stored unde
} else {
self.inner_max_degree as u64
};
- let degree = children.length();
+ let degree = children.length();
// See if the node is big enough, or we need to merge it with another node on this level.
let big_enough = degree * 2 >= max_degree;
@@ -2262,8 +2668,8 @@ Given a path to node (excluding the node itself), which is currently stored unde
// index of the node we will rebalance with.
let sibling_index = {
- let parent_children = &self.borrow_node(*path_to_node.borrow(path_to_node.length() - 1)).children;
- assert!(parent_children.length() >= 2, error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
+ let parent_children = &self.borrow_node(*path_to_node.borrow(path_to_node.length() - 1)).children;
+ assert!(parent_children.length() >= 2, error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
// If we are the largest node from the parent, we merge with the `prev`
// (which is then guaranteed to have the same parent, as any node has >1 children),
// otherwise we merge with `next`.
@@ -2280,7 +2686,7 @@ Given a path to node (excluding the node itself), which is currently stored unde
assert!(is_leaf == sibling_node.is_leaf, error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
let sibling_children = &mut sibling_node.children;
- if ((sibling_children.length() - 1) * 2 >= max_degree) {
+ if ((sibling_children.length() - 1) * 2 >= max_degree) {
// The sibling node has enough elements, we can just borrow an element from the sibling node.
if (sibling_index == next) {
// if sibling is the node with larger keys, we remove a child from the start
@@ -2374,92 +2780,6 @@ Given a path to node (excluding the node itself), which is currently stored unde
-
-
-
-
-## Function `length`
-
-Returns the number of elements in the BigOrderedMap.
-
-
-fun length<K: store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>): u64
-
-
-
-
-
-Implementation
-
-
-fun length<K: store, V: store>(self: &BigOrderedMap<K, V>): u64 {
- self.length_for_node(ROOT_INDEX)
-}
-
-
-
-
-
-
-
-
-## Function `length_for_node`
-
-
-
-fun length_for_node<K: store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>, node_index: u64): u64
-
-
-
-
-
-Implementation
-
-
-fun length_for_node<K: store, V: store>(self: &BigOrderedMap<K, V>, node_index: u64): u64 {
- let node = self.borrow_node(node_index);
- if (node.is_leaf) {
- node.children.length()
- } else {
- let size = 0;
-
- node.children.for_each_ref(|_key, child| {
- size = size + self.length_for_node(child.node_index.stored_to_index());
- });
- size
- }
-}
-
-
-
-
-
-
-
-
-## Function `is_empty`
-
-Returns true iff the BigOrderedMap is empty.
-
-
-fun is_empty<K: store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>): bool
-
-
-
-
-
-Implementation
-
-
-fun is_empty<K: store, V: store>(self: &BigOrderedMap<K, V>): bool {
- let node = self.borrow_node(self.min_leaf_index);
-
- node.children.is_empty()
-}
-
-
-
-
@@ -2500,22 +2820,6 @@ Returns true iff the BigOrderedMap is empty.
-pragma opaque;
-
-
-
-
-
-
-### Function `length_for_node`
-
-
-fun length_for_node<K: store, V: store>(self: &big_ordered_map::BigOrderedMap<K, V>, node_index: u64): u64
-
-
-
-
-
pragma opaque;
diff --git a/aptos-move/framework/aptos-framework/doc/ordered_map.md b/aptos-move/framework/aptos-framework/doc/ordered_map.md
index cd90763cdbfe86..81c6c5b1bdf8f5 100644
--- a/aptos-move/framework/aptos-framework/doc/ordered_map.md
+++ b/aptos-move/framework/aptos-framework/doc/ordered_map.md
@@ -49,6 +49,12 @@ allowing cleaner iterator APIs.
- [Function `append_disjoint`](#0x1_ordered_map_append_disjoint)
- [Function `append_impl`](#0x1_ordered_map_append_impl)
- [Function `trim`](#0x1_ordered_map_trim)
+- [Function `borrow_front`](#0x1_ordered_map_borrow_front)
+- [Function `borrow_back`](#0x1_ordered_map_borrow_back)
+- [Function `pop_front`](#0x1_ordered_map_pop_front)
+- [Function `pop_back`](#0x1_ordered_map_pop_back)
+- [Function `prev_key`](#0x1_ordered_map_prev_key)
+- [Function `next_key`](#0x1_ordered_map_next_key)
- [Function `lower_bound`](#0x1_ordered_map_lower_bound)
- [Function `find`](#0x1_ordered_map_find)
- [Function `new_begin_iter`](#0x1_ordered_map_new_begin_iter)
@@ -739,7 +745,7 @@ Takes all elements from other
and adds them to self
, r
loop {
let ord = cmp::compare(&self.entries[cur_i].key, &other_entries[other_i].key);
if (ord.is_gt()) {
- reverse_result.push_back(self.entries.pop_back());
+ reverse_result.push_back(self.entries.pop_back());
if (cur_i == 0) {
// make other_entries empty, and rest in entries.
// TODO cannot use mem::swap until it is public/released
@@ -753,10 +759,10 @@ Takes all elements from other
and adds them to self
, r
// is_lt or is_eq
if (ord.is_eq()) {
// we skip the entries one, and below put in the result one from other.
- overwritten.push_back(self.entries.pop_back());
+ overwritten.push_back(self.entries.pop_back());
};
- reverse_result.push_back(other_entries.pop_back());
+ reverse_result.push_back(other_entries.pop_back());
if (other_i == 0) {
other_entries.destroy_empty();
break;
@@ -805,6 +811,174 @@ After the call, the original map will be left containing the elements [0, at).
+
+
+
+
+## Function `borrow_front`
+
+
+
+public fun borrow_front<K, V>(self: &ordered_map::OrderedMap<K, V>): (&K, &V)
+
+
+
+
+
+Implementation
+
+
+public fun borrow_front<K, V>(self: &OrderedMap<K, V>): (&K, &V) {
+ let entry = self.entries.borrow(0);
+ (&entry.key, &entry.value)
+}
+
+
+
+
+
+
+
+
+## Function `borrow_back`
+
+
+
+public fun borrow_back<K, V>(self: &ordered_map::OrderedMap<K, V>): (&K, &V)
+
+
+
+
+
+Implementation
+
+
+public fun borrow_back<K, V>(self: &OrderedMap<K, V>): (&K, &V) {
+ let entry = self.entries.borrow(self.entries.length() - 1);
+ (&entry.key, &entry.value)
+}
+
+
+
+
+
+
+
+
+## Function `pop_front`
+
+
+
+public fun pop_front<K, V>(self: &mut ordered_map::OrderedMap<K, V>): (K, V)
+
+
+
+
+
+Implementation
+
+
+public fun pop_front<K, V>(self: &mut OrderedMap<K, V>): (K, V) {
+ let Entry { key, value } = self.entries.remove(0);
+ (key, value)
+}
+
+
+
+
+
+
+
+
+## Function `pop_back`
+
+
+
+public fun pop_back<K, V>(self: &mut ordered_map::OrderedMap<K, V>): (K, V)
+
+
+
+
+
+Implementation
+
+
+public fun pop_back<K, V>(self: &mut OrderedMap<K, V>): (K, V) {
+ let Entry { key, value } = self.entries.pop_back();
+ (key, value)
+}
+
+
+
+
+
+
+
+
+## Function `prev_key`
+
+
+
+public fun prev_key<K: copy, V>(self: &ordered_map::OrderedMap<K, V>, key: &K): option::Option<K>
+
+
+
+
+
+Implementation
+
+
+public fun prev_key<K: copy, V>(self: &OrderedMap<K, V>, key: &K): Option<K> {
+ let it = self.lower_bound(key);
+ if (it.iter_is_begin(self)) {
+ option::none()
+ } else {
+ option::some(*it.iter_prev(self).iter_borrow_key(self))
+ }
+}
+
+
+
+
+
+
+
+
+## Function `next_key`
+
+
+
+public fun next_key<K: copy, V>(self: &ordered_map::OrderedMap<K, V>, key: &K): option::Option<K>
+
+
+
+
+
+Implementation
+
+
+public fun next_key<K: copy, V>(self: &OrderedMap<K, V>, key: &K): Option<K> {
+ let it = self.lower_bound(key);
+ if (it.iter_is_end(self)) {
+ option::none()
+ } else {
+ let cur_key = it.iter_borrow_key(self);
+ if (key == cur_key) {
+ let it = it.iter_next(self);
+ if (it.iter_is_end(self)) {
+ option::none()
+ } else {
+ option::some(*it.iter_borrow_key(self))
+ }
+ } else {
+ option::some(*cur_key)
+ }
+ }
+}
+
+
+
+
@@ -1002,7 +1176,7 @@ Note: Requires that the map is not changed after the input iterator is generated
Returns whether the iterator is a begin iterator.
-public fun iter_is_begin<K, V>(self: &ordered_map::IteratorPtr, map: &ordered_map::OrderedMap<K, V>): bool
+public(friend) fun iter_is_begin<K, V>(self: &ordered_map::IteratorPtr, map: &ordered_map::OrderedMap<K, V>): bool
@@ -1011,7 +1185,7 @@ Returns whether the iterator is a begin iterator.
Implementation
-public fun iter_is_begin<K, V>(self: &IteratorPtr, map: &OrderedMap<K, V>): bool {
+public(friend) fun iter_is_begin<K, V>(self: &IteratorPtr, map: &OrderedMap<K, V>): bool {
if (self is IteratorPtr::End) {
map.is_empty()
} else {
diff --git a/aptos-move/framework/aptos-framework/sources/datastructures/big_ordered_map.move b/aptos-move/framework/aptos-framework/sources/datastructures/big_ordered_map.move
index 6d1fd915b17278..d57f5aaba4d9a5 100644
--- a/aptos-move/framework/aptos-framework/sources/datastructures/big_ordered_map.move
+++ b/aptos-move/framework/aptos-framework/sources/datastructures/big_ordered_map.move
@@ -29,8 +29,6 @@ module aptos_std::big_ordered_map {
use aptos_std::storage_slots_allocator::{Self, StorageSlotsAllocator, StoredSlot};
use aptos_std::math64::{max, min};
- friend aptos_framework::permissioned_signer;
-
// Error constants shared with ordered_map (so try using same values)
/// Map key already exists
@@ -60,13 +58,13 @@ module aptos_std::big_ordered_map {
// Internal constants.
const DEFAULT_TARGET_NODE_SIZE: u64 = 4096;
- const DEFAULT_INNER_MIN_DEGREE: u16 = 4;
+ const INNER_MIN_DEGREE: u16 = 4;
// We rely on 1 being valid size only for root node,
// so this cannot be below 3 (unless that is changed)
- const DEFAULT_LEAF_MIN_DEGREE: u16 = 3;
+ const LEAF_MIN_DEGREE: u16 = 3;
const MAX_DEGREE: u64 = 4096;
- const MAX_NODE_BYTES: u64 = 204800; // 200 KB, well bellow the max resource limit.
+ const MAX_NODE_BYTES: u64 = 409600; // 400 KB, bellow the max resource limit.
// Constants aligned with storage_slots_allocator
const NULL_INDEX: u64 = 0;
@@ -151,6 +149,7 @@ module aptos_std::big_ordered_map {
/// Only allowed to be called with constant size types. For variable sized types,
/// it is required to use new_with_config, to explicitly select automatic or specific degree selection.
public fun new(): BigOrderedMap {
+ // Use new_with_type_size_hints or new_with_config if your types have variable sizes.
assert!(
bcs::constant_serialized_size().is_some() && bcs::constant_serialized_size().is_some(),
error::invalid_argument(EINVALID_CONFIG_PARAMETER)
@@ -159,6 +158,29 @@ module aptos_std::big_ordered_map {
new_with_config(0, 0, false)
}
+ /// Returns a new BigOrderedMap, configured based on passed key and value serialized size hints.
+ public fun new_with_type_size_hints(avg_key_bytes: u64, max_key_bytes: u64, avg_value_bytes: u64, max_value_bytes: u64): BigOrderedMap {
+ assert!(avg_key_bytes <= max_key_bytes, error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+ assert!(avg_value_bytes <= max_value_bytes, error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+
+ let inner_max_degree_from_avg = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / avg_key_bytes), INNER_MIN_DEGREE as u64);
+ let inner_max_degree_from_max = MAX_NODE_BYTES / max_key_bytes;
+ assert!(inner_max_degree_from_max >= (INNER_MIN_DEGREE as u64), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+
+ let avg_entry_size = avg_key_bytes + avg_value_bytes;
+ let max_entry_size = max_key_bytes + max_value_bytes;
+
+ let leaf_max_degree_from_avg = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / avg_entry_size), LEAF_MIN_DEGREE as u64);
+ let leaf_max_degree_from_max = MAX_NODE_BYTES / max_entry_size;
+ assert!(leaf_max_degree_from_max >= (INNER_MIN_DEGREE as u64), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+
+ new_with_config(
+ min(inner_max_degree_from_avg, inner_max_degree_from_max) as u16,
+ min(leaf_max_degree_from_avg, leaf_max_degree_from_max) as u16,
+ false,
+ )
+ }
+
/// Returns a new BigOrderedMap with the provided max degree consts (the maximum # of children a node can have, both inner and leaf).
/// If 0 is passed, then it is dynamically computed based on size of first key and value.
///
@@ -167,9 +189,13 @@ module aptos_std::big_ordered_map {
/// `entry_size * leaf_max_degree <= MAX_NODE_BYTES`
/// If keys or values have variable size, and first element could be non-representative in size (i.e. smaller than future ones),
/// it is important to compute and pass inner_max_degree and leaf_max_degree based on the largest element you want to be able to insert.
+ ///
+ /// `reuse_slots` means that removing elements from the map doesn't free the storage slots and returns the refund.
+ /// Together with `allocate_spare_slots`, it allows to preallocate slots and have inserts have predictable gas costs.
+ /// (otherwise, inserts that require map to add new nodes, cost significantly more, compared to the rest)
public fun new_with_config(inner_max_degree: u16, leaf_max_degree: u16, reuse_slots: bool): BigOrderedMap {
- assert!(inner_max_degree == 0 || (inner_max_degree >= DEFAULT_INNER_MIN_DEGREE && (inner_max_degree as u64) <= MAX_DEGREE), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
- assert!(leaf_max_degree == 0 || (leaf_max_degree >= DEFAULT_LEAF_MIN_DEGREE && (leaf_max_degree as u64) <= MAX_DEGREE), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+ assert!(inner_max_degree == 0 || (inner_max_degree >= INNER_MIN_DEGREE && (inner_max_degree as u64) <= MAX_DEGREE), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
+ assert!(leaf_max_degree == 0 || (leaf_max_degree >= LEAF_MIN_DEGREE && (leaf_max_degree as u64) <= MAX_DEGREE), error::invalid_argument(EINVALID_CONFIG_PARAMETER));
// Assert that storage_slots_allocator special indices are aligned:
assert!(storage_slots_allocator::is_null_index(NULL_INDEX), error::invalid_state(EINTERNAL_INVARIANT_BROKEN));
@@ -215,6 +241,22 @@ module aptos_std::big_ordered_map {
self.nodes.allocate_spare_slots(num_to_allocate)
}
+ /// Returns true iff the BigOrderedMap is empty.
+ public fun is_empty(self: &BigOrderedMap): bool {
+ let node = self.borrow_node(self.min_leaf_index);
+ node.children.is_empty()
+ }
+
+ /// Returns the number of elements in the BigOrderedMap.
+ /// This is an expensive function, as it goes through all the leaves to compute it.
+ public fun compute_length(self: &BigOrderedMap): u64 {
+ let size = 0;
+ self.for_each_leaf_node_ref(|node| {
+ size += node.children.length();
+ });
+ size
+ }
+
// ======================= Section with Modifiers =========================
/// Inserts the key/value into the BigOrderedMap.
@@ -270,6 +312,20 @@ module aptos_std::big_ordered_map {
});
}
+ public fun pop_front(self: &mut BigOrderedMap): (K, V) {
+ let it = self.new_begin_iter();
+ let k = *it.iter_borrow_key();
+ let v = self.remove(&k);
+ (k, v)
+ }
+
+ public fun pop_back(self: &mut BigOrderedMap): (K, V) {
+ let it = self.new_end_iter().iter_prev(self);
+ let k = *it.iter_borrow_key();
+ let v = self.remove(&k);
+ (k, v)
+ }
+
// ============================= Accessors ================================
/// Returns an iterator pointing to the first element that is greater or equal to the provided
@@ -322,20 +378,116 @@ module aptos_std::big_ordered_map {
let iter = self.find(key);
assert!(!iter.iter_is_end(self), error::invalid_argument(EKEY_NOT_FOUND));
- // TODO cannot call iter_borrow, because reference checks assume return has reference to iter that is being destroyed
- // iter.iter_borrow(self)
-
- assert!(!iter.iter_is_end(self), error::invalid_argument(EITER_OUT_OF_BOUNDS));
- let children = &self.borrow_node(iter.node_index).children;
- &iter.child_iter.iter_borrow(children).value
+ iter.iter_borrow(self)
}
/// Returns a mutable reference to the element with its key at the given index, aborts if the key is not found.
+ /// Aborts with EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE if KV size doesn't have constant size,
+ /// because if it doesn't we cannot assert invariants on the size.
+ /// In case of variable size, use either `borrow`, `copy` then `upsert`, or `remove` and `add` instead of mutable borrow.
public fun borrow_mut(self: &mut BigOrderedMap, key: &K): &mut V {
let iter = self.find(key);
assert!(!iter.iter_is_end(self), error::invalid_argument(EKEY_NOT_FOUND));
iter.iter_borrow_mut(self)
}
+ public fun borrow_front(self: &BigOrderedMap): (K, &V) {
+ let it = self.new_begin_iter();
+ let key = *it.iter_borrow_key();
+ (key, it.iter_borrow(self))
+ }
+
+ public fun borrow_back(self: &BigOrderedMap): (K, &V) {
+ let it = self.new_end_iter().iter_prev(self);
+ let key = *it.iter_borrow_key();
+ (key, it.iter_borrow(self))
+ }
+
+ public fun prev_key(self: &BigOrderedMap, key: &K): Option {
+ let it = self.lower_bound(key);
+ if (it.iter_is_begin(self)) {
+ option::none()
+ } else {
+ option::some(*it.iter_prev(self).iter_borrow_key())
+ }
+ }
+
+ public fun next_key(self: &BigOrderedMap, key: &K): Option {
+ let it = self.lower_bound(key);
+ if (it.iter_is_end(self)) {
+ option::none()
+ } else {
+ let cur_key = it.iter_borrow_key();
+ if (key == cur_key) {
+ let it = it.iter_next(self);
+ if (it.iter_is_end(self)) {
+ option::none()
+ } else {
+ option::some(*it.iter_borrow_key())
+ }
+ } else {
+ option::some(*cur_key)
+ }
+ }
+ }
+
+ // =========================== Views and Traversals ==============================
+
+ /// Convert a BigOrderedMap to an OrderedMap, which is supposed to be called mostly by view functions to get an atomic
+ /// view of the whole map.
+ /// Disclaimer: This function may be costly as the BigOrderedMap may be huge in size. Use it at your own discretion.
+ public fun to_ordered_map(self: &BigOrderedMap): OrderedMap {
+ let result = ordered_map::new();
+ self.for_each_ref(|k, v| {
+ result.new_end_iter().iter_add(&mut result, *k, *v);
+ });
+ result
+ }
+
+ /// Get all keys.
+ ///
+ /// For a large enough BigOrderedMap this function will fail due to execution gas limits,
+ /// use iterartor or next_key/prev_key to iterate over across portion of the map.
+ public fun keys(self: &BigOrderedMap): vector {
+ let result = vector[];
+ self.for_each_ref(|k, _v| {
+ result.push_back(*k);
+ });
+ result
+ }
+
+ /// Apply the function to each element in the vector, consuming it, leaving the map empty.
+ public inline fun for_each_and_clear(self: &mut BigOrderedMap, f: |K, V|) {
+ // TODO - this can be done more efficiently, by destroying the leaves directly
+ // but that requires more complicated code and testing.
+ while (!self.is_empty()) {
+ let (k, v) = self.pop_front();
+ f(k, v);
+ };
+ }
+
+ /// Apply the function to each element in the vector, consuming it, and consuming the map
+ public inline fun for_each(self: BigOrderedMap, f: |K, V|) {
+ // TODO - this can be done more efficiently, by destroying the leaves directly
+ // but that requires more complicated code and testing.
+ self.for_each_and_clear(|k, v| f(k, v));
+ destroy_empty(self)
+ }
+
+ /// Apply the function to a reference of each element in the vector.
+ public inline fun for_each_ref(self: &BigOrderedMap, f: |&K, &V|) {
+ self.for_each_leaf_node_ref(|node| {
+ node.children.for_each_ref(|k: &K, v: &Child| {
+ f(k, &v.value);
+ });
+ })
+ }
+
+ /// Destroy a map, by destroying elements individually.
+ public inline fun destroy(self: BigOrderedMap, dv: |V|) {
+ for_each(self, |_k, v| {
+ dv(v);
+ });
+ }
// ========================= IteratorPtr functions ===========================
@@ -382,22 +534,26 @@ module aptos_std::big_ordered_map {
/// Borrows the value given iterator points to.
/// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end.
/// Note: Requires that the map is not changed after the input iterator is generated.
- public(friend) fun iter_borrow(self: &IteratorPtr, map: &BigOrderedMap): &V {
+ public(friend) fun iter_borrow(self: IteratorPtr, map: &BigOrderedMap): &V {
assert!(!self.iter_is_end(map), error::invalid_argument(EITER_OUT_OF_BOUNDS));
- let children = &map.borrow_node(self.node_index).children;
- &self.child_iter.iter_borrow(children).value
+ let IteratorPtr::Some { node_index, child_iter, key: _ } = self;
+ let children = &map.borrow_node(node_index).children;
+ &child_iter.iter_borrow(children).value
}
/// Mutably borrows the value iterator points to.
/// Aborts with EITER_OUT_OF_BOUNDS if iterator is pointing to the end.
/// Aborts with EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE if KV size doesn't have constant size,
- /// because if it doesn't - we need to call `upsert` to be able to check size invariants after modification.
+ /// because if it doesn't we cannot assert invariants on the size.
+ /// In case of variable size, use either `borrow`, `copy` then `upsert`, or `remove` and `add` instead of mutable borrow.
+ ///
/// Note: Requires that the map is not changed after the input iterator is generated.
- public(friend) fun iter_borrow_mut(self: &IteratorPtr, map: &mut BigOrderedMap): &mut V {
+ public(friend) fun iter_borrow_mut(self: IteratorPtr, map: &mut BigOrderedMap): &mut V {
assert!(map.constant_kv_size, error::invalid_argument(EBORROW_MUT_REQUIRES_CONSTANT_KV_SIZE));
assert!(!self.iter_is_end(map), error::invalid_argument(EITER_OUT_OF_BOUNDS));
- let children = &mut map.borrow_node_mut(self.node_index).children;
- &mut self.child_iter.iter_borrow_mut(children).value
+ let IteratorPtr::Some { node_index, child_iter, key: _ } = self;
+ let children = &mut map.borrow_node_mut(node_index).children;
+ &mut child_iter.iter_borrow_mut(children).value
}
/// Returns the next iterator.
@@ -460,38 +616,18 @@ module aptos_std::big_ordered_map {
new_iter(prev_index, child_iter, iter_key)
}
- /// Apply the function to each element in the vector, consuming it.
- public(friend) inline fun for_each(self: BigOrderedMap, f: |K, V|) {
- // TODO - this can be done more efficiently, by destroying the leaves directly
- // but that requires more complicated code and testing.
- let it = self.new_begin_iter();
- while (!it.iter_is_end(&self)) {
- let k = *it.iter_borrow_key();
- let v = self.remove(&k);
- f(k, v);
- it = self.new_begin_iter();
- };
- destroy_empty(self)
- }
+ // ====================== Internal Implementations ========================
- /// Apply the function to a reference of each element in the vector.
- public(friend) inline fun for_each_ref(self: &BigOrderedMap, f: |&K, &V|) {
- let it = self.new_begin_iter();
- while (!it.iter_is_end(self)) {
- f(it.iter_borrow_key(), it.iter_borrow(self));
- it = it.iter_next(self);
- };
- }
+ inline fun for_each_leaf_node_ref(self: &BigOrderedMap, f: |&Node|) {
+ let cur_node_index = self.min_leaf_index;
- /// Destroy a map, by destroying elements individually.
- public(friend) inline fun destroy(self: BigOrderedMap, dv: |V|) {
- for_each(self, |_k, v| {
- dv(v);
- });
+ while (cur_node_index != NULL_INDEX) {
+ let node = self.borrow_node(cur_node_index);
+ f(node);
+ cur_node_index = node.next;
+ }
}
- // ====================== Internal Implementations ========================
-
/// Borrow a node, given an index. Works for both root (i.e. inline) node and separately stored nodes
inline fun borrow_node(self: &BigOrderedMap, node_index: u64): &Node {
if (node_index == ROOT_INDEX) {
@@ -573,11 +709,11 @@ module aptos_std::big_ordered_map {
let entry_size = key_size + value_size;
if (self.inner_max_degree == 0) {
- self.inner_max_degree = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / key_size), DEFAULT_INNER_MIN_DEGREE as u64) as u16;
+ self.inner_max_degree = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / key_size), INNER_MIN_DEGREE as u64) as u16;
};
if (self.leaf_max_degree == 0) {
- self.leaf_max_degree = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / entry_size), DEFAULT_LEAF_MIN_DEGREE as u64) as u16;
+ self.leaf_max_degree = max(min(MAX_DEGREE, DEFAULT_TARGET_NODE_SIZE / entry_size), LEAF_MIN_DEGREE as u64) as u16;
};
// Make sure that no nodes can exceed the upper size limit.
@@ -1093,32 +1229,6 @@ module aptos_std::big_ordered_map {
old_child
}
- /// Returns the number of elements in the BigOrderedMap.
- fun length(self: &BigOrderedMap): u64 {
- self.length_for_node(ROOT_INDEX)
- }
-
- fun length_for_node(self: &BigOrderedMap, node_index: u64): u64 {
- let node = self.borrow_node(node_index);
- if (node.is_leaf) {
- node.children.length()
- } else {
- let size = 0;
-
- node.children.for_each_ref(|_key, child| {
- size = size + self.length_for_node(child.node_index.stored_to_index());
- });
- size
- }
- }
-
- /// Returns true iff the BigOrderedMap is empty.
- fun is_empty(self: &BigOrderedMap): bool {
- let node = self.borrow_node(self.min_leaf_index);
-
- node.children.is_empty()
- }
-
// ===== spec ===========
spec module {
@@ -1135,10 +1245,6 @@ module aptos_std::big_ordered_map {
pragma opaque;
}
- spec length_for_node {
- pragma opaque;
- }
-
// ============================= Tests ====================================
#[test_only]
@@ -1179,7 +1285,7 @@ module aptos_std::big_ordered_map {
#[test_only]
fun validate_iteration(self: &BigOrderedMap) {
- let expected_num_elements = self.length();
+ let expected_num_elements = self.compute_length();
let num_elements = 0;
let it = new_begin_iter(self);
while (!it.iter_is_end(self)) {
@@ -1300,6 +1406,22 @@ module aptos_std::big_ordered_map {
});
}
+ #[test]
+ fun test_for_each_ref() {
+ let map = new_with_config(4, 3, false);
+ map.add_all(vector[1, 3, 6, 2, 9, 5, 7, 4, 8], vector[1, 3, 6, 2, 9, 5, 7, 4, 8]);
+
+ let expected = vector[1, 2, 3, 4, 5, 6, 7, 8, 9];
+ let index = 0;
+ map.for_each_ref(|k, v| {
+ assert!(*k == expected[index], *k + 100);
+ assert!(*v == expected[index], *k + 200);
+ index += 1;
+ });
+
+ map.destroy(|_v| {});
+ }
+
#[test]
fun test_variable_size() {
let map = new_with_config, vector>(0, 0, false);
@@ -1468,6 +1590,37 @@ module aptos_std::big_ordered_map {
map.destroy(|_v| {});
}
+ #[test]
+ fun test_non_iterator_ordering() {
+ let map = new_from(vector[1, 2, 3], vector[10, 20, 30]);
+ assert!(map.prev_key(&1).is_none(), 1);
+ assert!(map.next_key(&1) == option::some(2), 1);
+
+ assert!(map.prev_key(&2) == option::some(1), 2);
+ assert!(map.next_key(&2) == option::some(3), 3);
+
+ assert!(map.prev_key(&3) == option::some(2), 4);
+ assert!(map.next_key(&3).is_none(), 5);
+
+ let (front_k, front_v) = map.borrow_front();
+ assert!(front_k == 1, 6);
+ assert!(front_v == &10, 7);
+
+ let (back_k, back_v) = map.borrow_back();
+ assert!(back_k == 3, 8);
+ assert!(back_v == &30, 9);
+
+ let (front_k, front_v) = map.pop_front();
+ assert!(front_k == 1, 10);
+ assert!(front_v == 10, 11);
+
+ let (back_k, back_v) = map.pop_back();
+ assert!(back_k == 3, 12);
+ assert!(back_v == 30, 13);
+
+ map.destroy(|_v| {});
+ }
+
#[test]
#[expected_failure(abort_code = 0x1000B, location = Self)] /// EINVALID_CONFIG_PARAMETER
fun test_inner_max_degree_too_large() {
@@ -1628,7 +1781,7 @@ module aptos_std::big_ordered_map {
fun test_adding_key_too_large() {
let map = new_with_config(0, 0, false);
map.add(vector[1], 1);
- map.add(vector_range(0, 57), 1);
+ map.add(vector_range(0, 143), 1);
map.destroy_and_validate();
}
@@ -1637,7 +1790,7 @@ module aptos_std::big_ordered_map {
fun test_adding_value_too_large() {
let map = new_with_config(0, 0, false);
map.add(1, vector[1]);
- map.add(2, vector_range(0, 107));
+ map.add(2, vector_range(0, 268));
map.destroy_and_validate();
}
diff --git a/aptos-move/framework/aptos-framework/sources/datastructures/ordered_map.move b/aptos-move/framework/aptos-framework/sources/datastructures/ordered_map.move
index 435be1ef324466..79a5f9654d3595 100644
--- a/aptos-move/framework/aptos-framework/sources/datastructures/ordered_map.move
+++ b/aptos-move/framework/aptos-framework/sources/datastructures/ordered_map.move
@@ -273,6 +273,54 @@ module aptos_std::ordered_map {
}
}
+ public fun borrow_front(self: &OrderedMap): (&K, &V) {
+ let entry = self.entries.borrow(0);
+ (&entry.key, &entry.value)
+ }
+
+ public fun borrow_back(self: &OrderedMap): (&K, &V) {
+ let entry = self.entries.borrow(self.entries.length() - 1);
+ (&entry.key, &entry.value)
+ }
+
+ public fun pop_front(self: &mut OrderedMap): (K, V) {
+ let Entry { key, value } = self.entries.remove(0);
+ (key, value)
+ }
+
+ public fun pop_back(self: &mut OrderedMap): (K, V) {
+ let Entry { key, value } = self.entries.pop_back();
+ (key, value)
+ }
+
+ public fun prev_key(self: &OrderedMap, key: &K): Option {
+ let it = self.lower_bound(key);
+ if (it.iter_is_begin(self)) {
+ option::none()
+ } else {
+ option::some(*it.iter_prev(self).iter_borrow_key(self))
+ }
+ }
+
+ public fun next_key(self: &OrderedMap, key: &K): Option {
+ let it = self.lower_bound(key);
+ if (it.iter_is_end(self)) {
+ option::none()
+ } else {
+ let cur_key = it.iter_borrow_key(self);
+ if (key == cur_key) {
+ let it = it.iter_next(self);
+ if (it.iter_is_end(self)) {
+ option::none()
+ } else {
+ option::some(*it.iter_borrow_key(self))
+ }
+ } else {
+ option::some(*cur_key)
+ }
+ }
+ }
+
// TODO: see if it is more understandable if iterator points between elements,
// and there is iter_borrow_next and iter_borrow_prev, and provide iter_insert.
@@ -349,7 +397,7 @@ module aptos_std::ordered_map {
}
/// Returns whether the iterator is a begin iterator.
- public fun iter_is_begin(self: &IteratorPtr, map: &OrderedMap): bool {
+ public(friend) fun iter_is_begin(self: &IteratorPtr, map: &OrderedMap): bool {
if (self is IteratorPtr::End) {
map.is_empty()
} else {
@@ -867,6 +915,35 @@ module aptos_std::ordered_map {
assert!(rest == new_from(vector[3], vector[30]), 2);
}
+ #[test]
+ fun test_non_iterator_ordering() {
+ let map = new_from(vector[1, 2, 3], vector[10, 20, 30]);
+ assert!(map.prev_key(&1).is_none(), 1);
+ assert!(map.next_key(&1) == option::some(2), 1);
+
+ assert!(map.prev_key(&2) == option::some(1), 2);
+ assert!(map.next_key(&2) == option::some(3), 3);
+
+ assert!(map.prev_key(&3) == option::some(2), 4);
+ assert!(map.next_key(&3).is_none(), 5);
+
+ let (front_k, front_v) = map.borrow_front();
+ assert!(front_k == &1, 6);
+ assert!(front_v == &10, 7);
+
+ let (back_k, back_v) = map.borrow_back();
+ assert!(back_k == &3, 8);
+ assert!(back_v == &30, 9);
+
+ let (front_k, front_v) = map.pop_front();
+ assert!(front_k == 1, 10);
+ assert!(front_v == 10, 11);
+
+ let (back_k, back_v) = map.pop_back();
+ assert!(back_k == 3, 12);
+ assert!(back_v == 30, 13);
+ }
+
#[test]
fun test_replace_key_inplace() {
let map = new_from(vector[1, 3, 5], vector[10, 30, 50]);
diff --git a/aptos-move/framework/aptos-stdlib/doc/smart_table.md b/aptos-move/framework/aptos-stdlib/doc/smart_table.md
index 83eb27ba47fff6..7598a018429423 100644
--- a/aptos-move/framework/aptos-stdlib/doc/smart_table.md
+++ b/aptos-move/framework/aptos-stdlib/doc/smart_table.md
@@ -10,6 +10,9 @@ when expanding to avoid unexpected gas cost.
SmartTable uses faster hash function SipHash instead of cryptographically secure hash functions like sha3-256 since
it tolerates collisions.
+DEPRECATED: since it's implementation is inneficient, it
+has been deprecated in favor of big_ordered_map.move
.
+
- [Struct `Entry`](#0x1_smart_table_Entry)
- [Struct `SmartTable`](#0x1_smart_table_SmartTable)
diff --git a/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_table.move b/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_table.move
index c9c36712738da9..edc0f201396de7 100644
--- a/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_table.move
+++ b/aptos-move/framework/aptos-stdlib/sources/data_structures/smart_table.move
@@ -4,6 +4,9 @@
/// when expanding to avoid unexpected gas cost.
/// SmartTable uses faster hash function SipHash instead of cryptographically secure hash functions like sha3-256 since
/// it tolerates collisions.
+///
+/// DEPRECATED: since it's implementation is inneficient, it
+/// has been deprecated in favor of `big_ordered_map.move`.
module aptos_std::smart_table {
use std::error;
use std::vector;