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_mapvec_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): boolcontains<K: copy + drop>(self: &VecSet<K>, key: &K): bool
结构大小size<K: copy, V>(self: &VecMap<K,V>): u64size<K: copy + drop>(self: &VecSet<K>): u64
判断结构是否为空is_empty<K: copy, V>(self: &VecMap<K,V>): boolis_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();
    }
}