Golang 使用Map构建Set类型的实现方法
前言
本篇主要给大家讲述了如何利用Go语言的语法特性实现Set类型的数据结构,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍吧。
需求
对于Set类型的数据结构,其实本质上跟List没什么多大的区别。无非是Set不能含有重复的Item的特性,Set有初始化、Add、Clear、Remove、Contains等操作。接下来看具体的实现方式分析吧。
实现
仍然按照已有的编程经验来联想如何实现基本Set功能,在Java中很容易知道HashSet的底层实现是HashMap,核心的就是用一个常量来填充Map键值对中的Value选项。除此之外,重点关注Go中Map的数据结构,Key是不允许重复的,如下所示:
|
1
2
3
4
5
6
7
|
m := map[string]string{ "1": "one", "2": "two", "1": "one", "3": "three", } fmt.Println(m) |
程序会直接报错,提示重复Key值,这样就非常符合Set的特性需求了。
定义
前面分析出Set的Value为固定的值,用一个常量替代即可。但是笔者分析的实现源码,用的是一个空结构体来实现的,如下所示:
|
1
2
3
4
5
6
7
|
// 空结构体var Exists = struct{}{}// Set is the main interfacetype Set struct { // struct为结构体类型的变量 m map[interface{}]struct{}} |
为了解决上面为什么用空结构体来做常量Value,先看下面的是测试:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import ( "fmt" "unsafe")// 定义非空结构体type S struct { a uint16 b uint32}func main() { var s S fmt.Println(unsafe.Sizeof(s)) // prints 8, not 6 var s2 struct{} fmt.Println(unsafe.Sizeof(s2)) // prints 0} |
打印出空结构体变量的内存占用大小为0,再看看下面这个测试:
|
1
2
3
4
|
a := struct{}{}b := struct{}{}fmt.Println(a == b) // truefmt.Printf("%p, %p\n", &a, &b) // 0x55a988, 0x55a988 |
很有趣,a和b竟然相等,并且a和b的地址也是一样的。现在各位应该明白了为什么会有:
|
1
|
var Exists = struct{}{} |
这样的常量也来填充所有Map的Value了吧,Go真是精彩!!!
初始化
Set类型数据结构的初始化操作,在声明的同时可以选择传入或者不传入进去。声明Map切片的时候,Key可以为任意类型的数据,用空接口来实现即可。Value的话按照上面的分析,用空结构体即可:
|
1
2
3
4
5
6
7
8
|
func New(items ...interface{}) *Set { // 获取Set的地址 s := &Set{} // 声明map类型的数据结构 s.m = make(map[interface{}]struct{}) s.Add(items...) return s} |
添加
简化操作可以添加不定个数的元素进入到Set中,用变长参数的特性来实现这个需求即可,因为Map不允许Key值相同,所以不必有排重操作。同时将Value数值指定为空结构体类型。
|
1
2
3
4
5
6
|
func (s *Set) Add(items ...interface{}) error { for _, item := range items { s.m[item] = Exists } return nil} |
包含
Contains操作其实就是查询操作,看看有没有对应的Item存在,可以利用Map的特性来实现,但是由于不需要Value的数值,所以可以用 _,ok来达到目的:
|
1
2
3
4
|
func (s *Set) Contains(item interface{}) bool { _, ok := s.m[item] return ok} |
长度和清除
获取Set长度很简单,只需要获取底层实现的Map的长度即可:
|
1
2
3
|
func (s *Set) Size() int { return len(s.m)} |
清除操作的话,可以通过重新初始化Set来实现,如下即为实现过程:
|
1
2
3
|
func (s *Set) Clear() { s.m = make(map[interface{}]struct{})} |
相等
判断两个Set是否相等,可以通过循环遍历来实现,即将A中的每一个元素,查询在B中是否存在,只要有一个不存在,A和B就不相等,实现方式如下所示:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func (s *Set) Equal(other *Set) bool { // 如果两者Size不相等,就不用比较了 if s.Size() != other.Size() { return false } // 迭代查询遍历 for key := range s.m { // 只要有一个不存在就返回false if !other.Contains(key) { return false } } return true} |
子集
判断A是不是B的子集,也是循环遍历的过程,具体分析在上面已经讲述过,实现方式如下所示:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func (s *Set) IsSubset(other *Set) bool { // s的size长于other,不用说了 if s.Size() > other.Size() { return false } // 迭代遍历 for key := range s.m { if !other.Contains(key) { return false } } return true} |
Ok,以上就是Go中Set的主要函数实现方式,还是很有意思的。继续加油。
Golang 使用Map构建Set类型的实现方法的更多相关文章
- golang中map并发读写问题及解决方法
一.map并发读写问题 如果map由多协程同时读和写就会出现 fatal error:concurrent map read and map write的错误 如下代码很容易就出现map并发读写问题 ...
- 工作随笔——Golang interface 转换成其他类型
新的公司,新的氛围.一年了,打算写点什么.so,那就写google的golang语言吧. 最最最基础的语法结构见go语言菜鸟教程 接下来写点菜鸟教程没有的. go语言的设计者认为:go语言必须让程序员 ...
- 总结golang之map
总结golang之map 2017年04月13日 23:35:53 趁年轻造起来 阅读数:18637 标签: golangmapgo 更多 个人分类: golang 版权声明:本文为博主原创文章, ...
- golang之map的使用声明
1.map的基本介绍 map是key-value数据结构,又称为字段或者关联数组.类似其它编程语言的集合,在编程中是经常使用到的 2.map的声明 1)基本语法 var map 变量名 map[key ...
- webservice返回值为Map类型的处理方法
在写一个webservice的时候,方法的返回值是一个复杂类型,处理方法是写一个结果类(Javabean)作为返回值.想着webservice方法返回值为Map的没写过,然后就试着写了一个简单的Dem ...
- Map和String类型之间的转换
前提是String的格式是map或json类型的 public static void main(String[] args) { Map<String,Object> map = new ...
- Golang中map的三种声明方式和简单实现增删改查
package main import ( "fmt" ) func main() { test3 := map[string]string{ "one": & ...
- golang 中 map 转 struct
golang 中 map 转 struct package main import ( "fmt" "github.com/goinggo/mapstructure&qu ...
- sql.Rows 转换为 []map[string]interface{} 类型
// *sql.Rows 转换为 []map[string]interface{}类型 func rows2maps(rows *sql.Rows) (res []map[string]interfa ...
随机推荐
- Eclipse导入Android项目的方法(转)
原文:http://www.cnblogs.com/SkyD/archive/2010/11/25/1887219.html 看网上流传的Eclipse导入项目的方法都是在新建Android程序时使用 ...
- 【BZOJ】4361: isn
题解 可以想一下保留一个长度为k的不降序列方案数是\(f[k] (n - k)!\) \(f[k]\)是有多少个长度为k的不降序列 我们去掉不合法的,一定是前一次操作的时候有一个长度为\(k + 1\ ...
- linux密码暴力破解机
linux 密码保存在 /etc/shadow shadow 文件的保存格式 python:$$mWSyC6Pv$hpMreQT77R9ML/Xx1QnRAow1tUTDjIowaTssV7bZw9S ...
- 001.MySQL高可用主从复制简介
一 简介 1.1 概述 Mysql内建的复制功能是构建大型,高性能应用程序的基础.将Mysql的数据分布在多个系统之上,这种分布的机制,是通过将Mysql的某一台主机的数据复制到其它主机(slaves ...
- JSP中的内置标记(JSP动作)和 EL表达式
一.JSP的内置标记(JSP动作) (一)JSP的内置标记都是以<jsp: 开始标记的 一般会用到<jsp:useBean/>,<jsp:setProperty/>,&l ...
- Redis高可用之哨兵模式Sentinel配置与启动(五)
0.Redis目录结构 1)Redis介绍及部署在CentOS7上(一) 2)Redis指令与数据结构(二) 3)Redis客户端连接以及持久化数据(三) 4)Redis高可用之主从复制实践(四) 5 ...
- python中的*和**参数传递机制
python的参数传递机制具有值传递(int.float等值数据类型)和引用传递(以字典.列表等非值对象数据类型为代表)两种基本机制以及方便的关键字传递特性(直接使用函数的形参名指定实参的传递目标,如 ...
- BZOJ 3400: [Usaco2009 Mar]Cow Frisbee Team 奶牛沙盘队 动态规划
3400: [Usaco2009 Mar]Cow Frisbee Team 奶牛沙盘队 题目连接: http://www.lydsy.com/JudgeOnline/problem.php?id=34 ...
- CentOS 7安装GitLab 11.4.5
安装GitLab没必要像网上说的配置那么复杂,也不要自行编译安装,直接使用Omnibus版本即可,也就是综合安装包,注意如下几点: 1.不需要替换Nginx,直接使用GitLab的原生集成,同时这种方 ...
- 使用CefSharp在.Net程序中嵌入Chrome浏览器(十)——独立文件夹部署
CefSharp本身携带了一大堆文件,这些文件默认直接释放在exe文件底下,这种方式本身没有什么问题,但多了一大堆文件后不是很好看.本文这里就介绍一个方法,使得可以将CEF相关的文件部署到独立的文件夹 ...