vec_map & vec_set
模块说明
Sui
的 映射(vec_map) 和 集合(vec_set) 是基于vector实现的数据结构vec_map
是一种映射结构,保证不包含重复的键,但是条目按照插入顺序排列,而不是按键的顺序。vec_set
是一种集合结构,保证不包含重复的键。- 所有的操作时间复杂度为
O(N)
,N
为映射或集合的大小 - 该结构提供方便的操作映射或集合的接口,如何需要按键的顺序排序的映射都需要手工额外处理
源码路径
方法图解
结构定义
VecMap
public struct VecMap<K: copy, V> has copy, drop, store {
contents: vector<Entry<K, V>>,
}
/// An entry in the map
public struct Entry<K: copy, V> has copy, drop, store {
key: K,
value: V,
}
VecSet
public struct VecSet<K: copy + drop> has copy, drop, store {
contents: vector<K>,
}
方法说明
功能 | vec_map | vec_set |
---|---|---|
创建空结构 | empty<K: copy, V>(): VecMap<K,V> | empty<K: copy + drop>(): VecSet<K> |
创建单元素结构 | ❎ | singleton<K: copy + drop>(key: K): VecSet<K> |
插入元素 | insert<K: copy, V>(self: &mut VecMap<K,V>, key: K, value: V) | insert<K: copy + drop>(self: &mut VecSet<K>, key: K) |
移除元素 | remove<K: copy, V>(self: &mut VecMap<K,V>, key: &K): (K, V) | remove<K: copy + drop>(self: &mut VecSet<K>, key: &K) |
是否包含元素 | contains<K: copy, V>(self: &VecMap<K, V>, key: &K): bool | contains<K: copy + drop>(self: &VecSet<K>, key: &K): bool |
结构大小 | size<K: copy, V>(self: &VecMap<K,V>): u64 | size<K: copy + drop>(self: &VecSet<K>): u64 |
判断结构是否为空 | is_empty<K: copy, V>(self: &VecMap<K,V>): bool | is_empty<K: copy + drop>(self: &VecSet<K>): bool |
转换为元素数组 | into_keys_values<K: copy, V>(self: VecMap<K, V>): (vector<K>, vector<V>) | into_keys<K: copy + drop>(self: VecSet<K>): vector<K> |
根据元素数组进行构造 | from_keys_values<K: copy, V>( mut keys: vector<K>, mut values: vector<V>,) | from_keys<K: copy + drop>(mut keys: vector<K>): VecSet<K> |
获取键数组 | keys<K: copy, V>(self: &VecMap<K, V>): vector<K> | keys<K: copy + drop>(self: &VecSet<K>): &vector<K> |
按插入顺序获取指定键的坐标 | ① get_idx<K: copy, V>(self: &VecMap<K,V>, key: &K): u64 ② get_idx_opt<K: copy, V>(self: &VecMap<K,V>, key: &K): Option<u64> | ① get_idx<K: copy + drop>(self: &VecSet<K>, key: &K): u64 ② get_idx_opt<K: copy + drop>(self: &VecSet<K>, key: &K): Option<u64> |
销毁空结构 | destroy_empty<K: copy, V>(self: VecMap<K, V>) | ❎ |
弹出最新插入元素 | pop<K: copy, V>(self: &mut VecMap<K,V>): (K, V) | ❎ |
获取键对应值 | ① get<K: copy, V>(self: &VecMap<K,V>, key: &K): &V ② get_mut<K: copy, V>(self: &mut VecMap<K,V>, key: &K): &mut V ③ try_get<K: copy, V: copy>(self: &VecMap<K,V>, key: &K): Option<V> | ❎ |
按插入顺序索引键值对 | ① get_entry_by_idx<K: copy, V>(self: &VecMap<K, V>, idx: u64): (&K, &V) ② get_entry_by_idx_mut<K: copy, V>(self: &mut VecMap<K, V>, idx: u64): (&K, &mut V) | ❎ |
根据索引坐标移除键值对 | remove_entry_by_idx<K: copy, V>(self: &mut VecMap<K, V>, idx: u64): (K, V) | ❎ |
代码示例
示例中定义了一个书架结构(Bookshelf
),其中包含一个Bag
类型的items
,可以放入任意类型的键值对。示例中以书籍和玩具为例。
结构定义
public struct Bookshelf has key {
id: UID,
books: VecMap<String, Book>,
book_names: VecSet<String>,
}
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
创建书架共享对象
调用
vec_map::empty()
和vec_set::empty()
方法
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
books: vec_map::empty(),
book_names: vec_set::empty(),
});
}
放置书本到书架
调用
vec_map::insert()
和vec_set::insert()
方法
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>,
description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
bookshelf.books.insert(ascii::string(title), book);
bookshelf.book_names.insert(ascii::string(title));
}
获取书本
调用
vec_map::get()
和vec_map::pop()
方法。
public fun get_book(bookshelf: &Bookshelf, title: &String): &Book {
bookshelf.books.get(title)
}
public fun get_last_book(bookshelf: &mut Bookshelf): Book {
let (_, book) = bookshelf.books.pop();
book
}
设置书本的描述信息
调用
vec_map::get_mut()
方法。
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref: &mut Book = bookshelf.books.get_mut(&ascii::string(title));
book_mut_ref.description = ascii::string(description);
}
判断书本是否存在
调用
vec_map::contains()
或vec_set::contains()
方法
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
// bookshelf.book_names.contains(&ascii::string(title))
bookshelf.books.contains(&ascii::string(title))
}
从书架上移除书本
调用
vec_map::remove()
和vec_map::remove()
方法。
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.book_names.remove(&ascii::string(title));
let (_, book) = bookshelf.books.remove(&ascii::string(title));
book
}
判断书架是否为空
调用
vec_map::is_empty()
或vec_map::is_empty()
方法。
public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
// bookshelf.book_names.is_empty()
bookshelf.books.is_empty()
}
获取书本数量
调用
vec_map::size()
或vec_map::size()
方法。
public fun get_book_count(bookshelf: &Bookshelf): u64{
// bookshelf.book_names.size()
bookshelf.books.size()
}
销毁空书架
调用
vec_map::destroy_empty
方法。
public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
let Bookshelf {id, books, book_names:_} = bookshelf;
books.destroy_empty();
id.delete()
}
完整代码
- vec_map_and_vec_set
module cookbook::vec_map_and_vec_set{
use std::ascii::{Self, String};
use sui::vec_map::{Self, VecMap};
use sui::vec_set::{Self, VecSet};
public struct Bookshelf has key {
id: UID,
books: VecMap<String, Book>,
book_names: VecSet<String>,
}
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
// 创建书架共享对象
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
books: vec_map::empty(),
book_names: vec_set::empty(),
});
}
// 销毁空书架
public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
let Bookshelf {id, books, book_names:_} = bookshelf;
books.destroy_empty();
id.delete()
}
// 放置书本到书架
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>,
description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
bookshelf.books.insert(ascii::string(title), book);
bookshelf.book_names.insert(ascii::string(title));
}
// 拿取书本
public fun get_book(bookshelf: &Bookshelf, title: &String): &Book {
bookshelf.books.get(title)
}
// 取出最后一本放到书架上的书
public fun get_last_book(bookshelf: &mut Bookshelf): Book {
let (_, book) = bookshelf.books.pop();
book
}
// 设置书本的描述信息
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref: &mut Book = bookshelf.books.get_mut(&ascii::string(title));
book_mut_ref.description = ascii::string(description);
}
// 判断书本是否存在
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
// bookshelf.book_names.contains(&ascii::string(title))
bookshelf.books.contains(&ascii::string(title))
}
// 判断书架是否为空
public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
// bookshelf.book_names.is_empty()
bookshelf.books.is_empty()
}
// 从书架上移除书本
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.book_names.remove(&ascii::string(title));
let (_, book) = bookshelf.books.remove(&ascii::string(title));
book
}
public fun get_book_count(bookshelf: &Bookshelf): u64{
// bookshelf.book_names.size()
bookshelf.books.size()
}
public fun get_book_title(book: &Book): String {
book.title
}
public fun get_book_desc(book: &Book): String {
book.description
}
}
- vec_map_and_vec_set_tests
#[test_only]
module cookbook::vec_map_and_vec_set_tests {
use std::ascii;
use sui::test_scenario as ts;
use cookbook::vec_map_and_vec_set::{Bookshelf, create_bookshelf, destroy_empty_bookshelf,
add_book, get_book, set_book_desc, is_book_existed, remove_book, is_bookshelf_empty,
get_book_count, get_book_title, get_book_desc};
#[test_only]
use sui::test_utils::assert_eq;
#[test]
public fun test_vec_map() {
let alice = @0xa;
let mut ts = ts::begin(alice);
// 创建书架
{
create_bookshelf(ts.ctx());
};
// 放置书本到书架
let expected_title = b"Mastering Bitcoin";
let expected_description= b"1st Edition";
let expected_new_description= b"3rd Edition";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title,
expected_description,
ts.ctx(),
);
assert_eq(bookshelf.get_book_count(), 1);
assert_eq(bookshelf.is_bookshelf_empty(), false);
ts::return_shared(bookshelf);
};
// 放置书本2到书架
let expected_title2 = b"Move Cookbook";
let expected_description2= b"1st Edition";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title2,
expected_description2,
ts.ctx(),
);
assert_eq(bookshelf.get_book_count(), 2);
ts::return_shared(bookshelf);
};
// 拿取书本
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let book = get_book(
&bookshelf,
&ascii::string(expected_title),
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_description));
ts::return_shared(bookshelf);
};
// 设置书本的描述信息
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
set_book_desc(
&mut bookshelf,
expected_title,
expected_new_description,
);
let book = get_book(
&bookshelf,
&ascii::string(expected_title),
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
ts::return_shared(bookshelf);
};
// 判断书本是否存在
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let is_existed = is_book_existed(
&bookshelf,
expected_title,
);
assert_eq(is_existed, true);
ts::return_shared(bookshelf);
};
// 从书架上借走书本
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
{
assert_eq(bookshelf.get_book_count(), 2);
let book = remove_book(
&mut bookshelf,
expected_title,
);
assert_eq(bookshelf.get_book_count(), 1);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
transfer::public_transfer(book, alice);
};
{
assert_eq(bookshelf.get_book_count(), 1);
let book = remove_book(
&mut bookshelf,
expected_title2,
);
assert_eq(bookshelf.get_book_count(), 0);
assert_eq(book.get_book_title(), ascii::string(expected_title2));
assert_eq(book.get_book_desc(), ascii::string(expected_description2));
transfer::public_transfer(book, alice);
};
ts::return_shared(bookshelf);
};
// 销毁书架
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
destroy_empty_bookshelf(bookshelf);
};
ts.end();
}
}