{"author":{"address":"0xf3496C81f854a512F9E3722E0E044eC6b97b8BF9","user":"https://learnblockchain.cn/people/957"},"content":{"body":"# move入门之集合(三) : Vector数据衍生类型VecMap深度分析\r\n**🧑‍💻作者：gracecampo**\r\n## 概述\r\n\r\n之前的文章中，我们学习了vector,vecSet两种常用的集合数据结构，此章我们通过对前两种数据结构的回顾以及对比，探究下vecSet这种集合类型。\r\n\r\n## VecMap是什么？\r\n\r\n所属模块及包:    Module 0x2::vec_map\r\n\r\n**VecSet的特点:**\r\n\r\n由向量\u003cvector\u003e支持的map(即key-value)数据结构。\r\n\r\n**在此数据中键(key)是唯一的，不允许重复。\r\n\r\n数据是按照插入顺序排序，可根据key值获取相关value值。\r\n\r\n时间复杂度与vecSet一致，均为O（N），vecSet不同是是vecMap是一种键值对的数据结构，而vecSet是值即是key，key也是值。\r\n\r\n### 定义和语法\r\n#### 如何去声明一个vecSet:\r\n```move\r\n      let vec_map = vec_map::empty\u003c\u003e();\r\n```\r\n\r\n我们通过调用vec_map的empty函数，可以声明一个空的vec_map集合。\r\n```move\r\nlet vec_map = vec_map::empty\u003cu8,String\u003e();\r\n```\r\n\r\n当然，我们可以显式指定该集合种key与value的类型。\r\n\r\n#### \r\n\r\n## VecMap是如何实现的？\r\n接下来，我们通过对源码层面的刨析，探究它是如何实现的。\r\n### 结构体的声明\r\n```move\r\npublic struct VecMap\u003cK: copy, V\u003e has copy, drop, store {\r\n    contents: vector\u003cEntry\u003cK, V\u003e\u003e,\r\n}\r\n\r\n/// An entry in the map\r\npublic struct Entry\u003cK: copy, V\u003e has copy, drop, store {\r\n    key: K,\r\n    value: V,\r\n}\r\n```\r\n我们可以看到，在源码中，声明了一个Entry结构体，它包含两个元素，分别的key,value，而key,value都定义为一个泛型对象K ,V \r\n\r\n而主要结构体（即定义vecMap）中实际是通过将Entry放入到Vector容器中，从而实现map类型的支持。\r\n\r\n需要注意的点是： key必须是拥有copy能力的类型。\r\n\r\n### 异常码定义\r\n可以看到，在VecMap中声明了6种常量，来标识不同的异常信息\r\n```move\r\n///当插入map中已经存在key时异常码\r\nconst EKeyAlreadyExists: u64 = 0;\r\n\r\n/// 用户在获取不存在key时异常码\r\nconst EKeyDoesNotExist: u64 = 1;\r\n\r\n/// 销毁不为空的map时的异常码\r\nconst EMapNotEmpty: u64 = 2;\r\n\r\n/// 当获取或者移除的数据下标超出map集合时的异常码\r\nconst EIndexOutOfBounds: u64 = 3;\r\n\r\n/// 当尝试移除空集合末尾数据时的异常码\r\nconst EMapEmpty: u64 = 4;\r\n\r\n/// 当尝试通过keys 和values组装maps时，传入的keys 和values不一致时的异常码\r\nconst EUnequalLengths: u64 = 5;\r\n```\r\n### 函数定义\r\n\r\n创建一个空的map集合\r\n```move\r\npublic fun empty\u003cK: copy, V\u003e(): VecMap\u003cK, V\u003e {\r\n    VecMap { contents: vector[] }\r\n}\r\n```\r\n向map中添加元素  : 参数为: 1.可变引用map,2. 添加元素的key 3.添加元素的value\r\n当元素中已存在传入的key时，将提示异常码：`EKeyAlreadyExists`\r\n**注意点： 传入的map必须为可变引用类型  key 必须时用户copy能力的类型**\r\n```move\r\npublic fun insert\u003cK: copy, V\u003e(self: \u0026mut VecMap\u003cK, V\u003e, key: K, value: V) {\r\n    assert!(!self.contains(\u0026key), EKeyAlreadyExists);\r\n    self.contents.push_back(Entry { key, value })\r\n}\r\n```\r\n根据key移除相关元素\r\n通过调用get_idx函数，获取相关key的小标，通过下标将数据移除，并返回移除元素信息\r\n当传入key不存在时，将提示异常码：`EKeyDoesNotExist`\r\n```move\r\n/// Remove the entry `key` |-\u003e `value` from self. Aborts if `key` is not bound in `self`.\r\npublic fun remove\u003cK: copy, V\u003e(self: \u0026mut VecMap\u003cK, V\u003e, key: \u0026K): (K, V) {\r\n    let idx = self.get_idx(key);\r\n    let Entry { key, value } = self.contents.remove(idx);\r\n    (key, value)\r\n}\r\n```\r\n\r\n根据key,判断map元素中，是否存在相同的key元素\r\n```move\r\n/// Return true if `self` contains an entry for `key`, false otherwise\r\npublic fun contains\u003cK: copy, V\u003e(self: \u0026VecMap\u003cK, V\u003e, key: \u0026K): bool {\r\n    get_idx_opt(self, key).is_some()\r\n}\r\n```\r\n\r\n返回map的长度  参数：引用map\r\n```move\r\npublic fun size\u003cK: copy, V\u003e(self: \u0026VecMap\u003cK, V\u003e): u64 {\r\n    self.contents.length()\r\n}\r\n```\r\n\r\n析构map的key和value,并返回key集合和value集合\r\n```move\r\npublic fun into_keys_values\u003cK: copy, V\u003e(self: VecMap\u003cK, V\u003e): (vector\u003cK\u003e, vector\u003cV\u003e) {\r\n    let VecMap { mut contents } = self;\r\n    contents.reverse();\r\n    let mut i = 0;\r\n    let n = contents.length();\r\n    let mut keys = vector[];\r\n    let mut values = vector[];\r\n    while (i \u003c n) {\r\n        let Entry { key, value } = contents.pop_back();\r\n        keys.push_back(key);\r\n        values.push_back(value);\r\n        i = i + 1;\r\n    };\r\n    contents.destroy_empty();\r\n    (keys, values)\r\n}\r\n```\r\n用于将两个vector向量合并为一个vecMap\r\n```move\r\npublic fun from_keys_values\u003cK: copy, V\u003e(mut keys: vector\u003cK\u003e, mut values: vector\u003cV\u003e): VecMap\u003cK, V\u003e {\r\n    assert!(keys.length() == values.length(), EUnequalLengths);\r\n    //通过反转keys和values元素顺序，保证在处理过程中，最终返回的元素顺序\r\n    //是按照keys和values添加顺序组合\r\n    keys.reverse();\r\n    values.reverse();\r\n    let mut map = empty();\r\n    while (keys.length() != 0) map.insert(keys.pop_back(), values.pop_back());\r\n    keys.destroy_empty();\r\n    values.destroy_empty();\r\n    map\r\n}\r\n```\r\n当然，Module 0x2::vec_map中，对于vecMap的操作函数还有很多，比如获取下标，判断map是否为空，销毁空的map等等，\r\n\r\n如果同学感兴趣，可以进行自行翻阅相关源码。本节只对日常使用频繁的函数做下说明。\r\n\r\n## VecMap应该如何使用？\r\n接下来，我们开始实战环节\r\n\r\n声明一个空map，并添加元素进入\r\n\r\n**insert函数**\r\n\r\n```move\r\n//声明一个空的map对象\r\nlet mut vec_map = vec_map::empty\u003cString,String\u003e();\r\n//添加一个键值对\r\nvec_map.insert(string(b\"key1\"),string(b\"value1\"));\r\nvec_map.insert(string(b\"key2\"),string(b\"value2\"));\r\ndebug::print(\u0026vec_map);\r\n```\r\n我们通过debug::print 打印出vec_map中的元素：\r\n\r\n![img.png](image/vec_map.png)\r\n\r\n可以看到，我们已经将键值对`key1,key2`添加进入。\r\ninsert需要注意的是，不能存放相同key的数据，不然抛出错误信息。\r\n\r\n```move\r\nvec_map.insert(string(b\"key1\"),string(b\"value1\"));\r\nvec_map.insert(string(b\"key1\"),string(b\"value2\"));\r\n```\r\n比如，我们将`key2`改为`key1`,在进行测试时，测试将会失败。\r\n\r\n\u003e\u003eTest result: FAILED. Total tests: 1; passed: 0; failed: 1\r\n\r\n**remove函数**\r\n```move\r\n//声明一个空的map对象\r\nlet mut vec_map = vec_map::empty\u003cString,String\u003e();\r\n//添加一个键值对\r\nvec_map.insert(string(b\"key1\"),string(b\"value1\"));\r\n//删除key=key1的数据\r\nvec_map.remove(\u0026string(b\"key1\"));\r\ndebug::print(\u0026vec_map);\r\n```\r\n我们通过先添加一个`string(b\"key1\"),string(b\"value1\")`,然后通过`remove`函数进行移除元素`key1`。\r\n当我们运行测试时,vec_map容器内元素`key1`已经被移除：\r\n\r\n\u003e\u003e  Running Move unit tests\r\n\u003e\u003e  [debug] 0x2::vec_map::VecMap\u003c0x1::ascii::String, 0x1::ascii::String\u003e {\r\n\u003e\u003e  contents: []\r\n\u003e\u003e  }\r\n\u003e\u003e  [ PASS    ] hello::hello::test_vec_map\r\n\u003e\u003e  Test result: OK. Total tests: 1; passed: 1; failed: 0\r\n\r\n**contains函数**\r\n```move\r\n//声明一个空的map对象\r\nlet mut vec_map = vec_map::empty\u003cString,String\u003e();\r\n//添加一个键值对\r\nvec_map.insert(string(b\"key1\"),string(b\"value1\"));\r\n//通过调用contains函数，并传入key值，判断map容器中是否包含元素key1\r\nlet contains = vec_map.contains(\u0026string(b\"key1\"));\r\ndebug::print(\u0026contains);\r\n```\r\n\r\n\u003e\u003e  Running Move unit tests\r\n\u003e\u003e  [debug] true\r\n\u003e\u003e  [ PASS    ] hello::hello::test_vec_map\r\n\u003e\u003e  Test result: OK. Total tests: 1; passed: 1; failed: 0\r\n\r\n\r\n**size函数**\r\n```move\r\n//声明一个空的map对象\r\nlet mut vec_map = vec_map::empty\u003cString,String\u003e();\r\n//添加一个键值对\r\nvec_map.insert(string(b\"key1\"),string(b\"value1\"));\r\n//通过调用map_size函数，返回map中元素个数\r\nlet map_size = vec_map.size();\r\ndebug::print(\u0026map_size);\r\n```\r\n通过调用map_size函数，返回map中元素个数\r\n\r\ndebug打印出的元素个数符合我们预期 `1`\r\n\r\n\u003e\u003eRunning Move unit tests\r\n\u003e\u003e[debug] 1\r\n\u003e\u003e[ PASS    ] hello::hello::test_vec_map\r\n\u003e\u003eTest result: OK. Total tests: 1; passed: 1; failed: 0\r\n\r\n**from_keys_values函数**\r\n```move\r\n//声明两个向量，将vec_key元素作为key,vec_value元素作为value,将两向量合并为一个vec_maps\r\nlet mut vec_key = vector[string(b\"key1\"),string(b\"key2\")];\r\nlet mut  vec_value = vector[string(b\"value1\"),string(b\"value2\")];\r\nlet s = vec_map::from_keys_values\u003cString,String\u003e(vec_key,vec_value);\r\ndebug::print(\u0026s);\r\n```\r\n通过声明两个向量，将vec_key元素作为map的key,vec_value元素作为map的value,将两向量合并为一个vec_maps\r\n\r\n**需要注意的时:vec_key元素 和vec_value 必须长度一致，否则将抛出异常**\r\n\r\n![from_keys_values.png](https://img.learnblockchain.cn/attachments/2024/12/x7k1IesO6754317b61646.png)\r\n\r\n**into_keys_values**\r\n```move\r\n//声明两个向量，将vec_key元素作为key,vec_value元素作为value,将两向量合并为一个vec_maps\r\nlet mut vec_key = vector[string(b\"key1\"),string(b\"key2\")];\r\nlet mut  vec_value = vector[string(b\"value1\"),string(b\"value2\")];\r\nlet s = vec_map::from_keys_values\u003cString,String\u003e(vec_key,vec_value);\r\n//调用into_keys_values函数，将map的key和value拆分，并以元组形式返回\r\nlet (keys, values) = s.into_keys_values();\r\ndebug::print(\u0026keys);\r\ndebug::print(\u0026values);\r\n```\r\n我们先通过`from_keys_values` 组装一个vec_map,然后通过`into_keys_values`将map中的key和value进行拆分，并通过元组的形式返回\r\n此方法用于当我们需要分开获取key 和value时比较有用\r\n![into_keys_values.png](https://img.learnblockchain.cn/attachments/2024/12/xKnOueVm6754316d32a61.png)\r\n## 总结\r\n通过对vec_map的语法以及函数方法的学习，我们可以对map元素进行修改，也可以在需要组装一个map对象，一个析构一个map对象的函数，进行的讲解和实战，\r\n\r\n在日常的开发中，我们可能需要对一些向量对象进行处理，此时vec_map将是不二的数据类型，方便我们声明一些关系型的数据，不需要辛苦维护数据关系，\r\n\r\n通过将对象封装为一个vec_map对象，我们可以方便的进行数据操作。\r\n\r\n\r\n\r\n💧  [HOH水分子公众号](https://mp.weixin.qq.com/s/d0brr-ao6cZ5t8Z5OO1Mog)\r\n\r\n🌊  [HOH水分子X账号](https://x.com/0xHOH)\r\n\r\n📹  [课程B站账号](https://space.bilibili.com/3493269495352098)\r\n\r\n💻  Github仓库 https://github.com/move-cn/letsmove","title":"move入门之集合(三) : Vector数据衍生类型VecMap深度分析"},"history":null,"timestamp":1733570969,"version":1}