Go 小知识之 Go 中如何使用 set

Go 的数据结构

Go 内置的数据结构并不多。工作中,我们最常用的两种数据结构分别是 slice 和 map,即切片和映射。 其实,Go 中也有数组,切片的底层就是数组,只不过因为切片的存在,我们平时很少使用它。
除了 Go 内置的数据结构,还有一些数据结构是由 Go 的官方 container 包提供,如 heap 堆、list 双向链表和ring 回环链表。但今天我们不讲它们,这些数据结构,对于熟手来说,看看文档就会使用了。
我们今天将来聊的是 set 和 bitset。据我所知,其他一些语言,比如 Java,是有这两种数据结构。但 Go 当前还没有以任何形式提供。
实现思路
先来看一篇文章,访问地址 2 basic set implementations 阅读。文中介绍了两种 go 实现 set 的思路, 分别是 map 和 bitset。
有兴趣可以读读这篇文章,我们接下来具体介绍下。
map
我们知道,map 的 key 肯定是唯一的,而这恰好与 set 的特性一致,天然保证 set 中成员的唯一性。而且通过 map 实现 set,在检查是否存在某个元素时可直接使用 _, ok := m[key] 的语法,效率高。
先来看一个简单的实现,如下:

set := make(map[string]bool) // New empty set
set["Foo"] = true            // Add
for k := range set {         // Loopfmt.Println(k)
}
delete(set, "Foo")    // Delete
size := len(set)      // Size
exists := set["Foo"]  // Membership

通过创建 map[string]bool 来存储 string 的集合,比较容易理解。但这里还有个问题,map 的 value 是布尔类型,这会导致 set 多占一定内存空间,而 set 不该有这个问题。
怎么解决这个问题?
设置 value 为空结构体,在 Go 中,空结构体不占任何内存。当然,如果不确定,也可以来证明下这个结论。
复制代码unsafe.Sizeof(struct{}{}) // 结果为 0
优化后的代码,如下:

type void struct{}
var member voidset := make(map[string]void) // New empty set
set["Foo"] = member          // Add
for k := range set {         // Loopfmt.Println(k)
}
delete(set, "Foo")      // Delete
size := len(set)        // Size
_, exists := set["Foo"] // Membership

之前在网上看到有人按这个思路做了封装,还写了一篇文章,可以去读一下。
其实,github 上已经有个成熟的包,名为 golang-set,它也是采用这个思路实现的。访问地址 golang-set,描述中说 Docker 用的也是它。包中提供了两种 set 实现,线程安全的 set 和非线程安全的 set。
演示一个简单的案例。
复制代码package main

import ("fmt"mapset "github.com/deckarep/golang-set"
)
func main() {// 默认创建的线程安全的,如果无需线程安全// 可以使用 NewThreadUnsafeSet 创建,使用方法都是一样的。s1 := mapset.NewSet(1, 2, 3, 4)  fmt.Println("s1 contains 3: ", s1.Contains(3))fmt.Println("s1 contains 5: ", s1.Contains(5))// interface 参数,可以传递任意类型s1.Add("poloxue")fmt.Println("s1 contains poloxue: ", s1.Contains("poloxue"))s1.Remove(3)fmt.Println("s1 contains 3: ", s1.Contains(3))s2 := mapset.NewSet(1, 3, 4, 5)// 并集fmt.Println(s1.Union(s2))
}

输出如下:
复制代码s1 contains 3: true
s1 contains 5: false
s1 contains poloxue: true
s1 contains 3: false
Set{4, polxue, 1, 2, 3, 5}
例子中演示了简单的使用方式,如果有不明白的,看下源码,这些数据结构的操作方法名都是很常见的,比如交集 Intersect、差集 Difference 等,一看就懂。
bitset
继续聊聊 bitset,bitset 中每个数子用一个 bit 即能表示,对于一个 int8 的数字,我们可以用它表示 8 个数字,能帮助我们大大节省数据的存储空间。
bitset 最常见的应用有 bitmap 和 flag,即位图和标志位。这里,我们先尝试用它表示一些操作的标志位。比如某个场景,我们需要三个 flag 分别表示权限1、权限2和权限3,而且几个权限可以共存。我们可以分别用三个常量 F1、F2、F3 表示位 Mask。

type Bits uint8const (F0 Bits = 1 << iotaF1F2
)func Set(b, flag Bits) Bits    { return b | flag }
func Clear(b, flag Bits) Bits  { return b &^ flag }
func Toggle(b, flag Bits) Bits { return b ^ flag }
func Has(b, flag Bits) bool    { return b&flag != 0 }func main() {var b Bitsb = Set(b, F0)b = Toggle(b, F2)for i, flag := range []Bits{F0, F1, F2} {fmt.Println(i, Has(b, flag))}
}

例子中,我们本来需要三个数才能表示这三个标志,但现在通过一个 uint8 就可以。bitset 的一些操作,如设置 Set、清除 Clear、切换 Toggle、检查 Has 通过位运算就可以实现,而且非常高效。
bitset 对集合操作有着天然的优势,直接通过位运算符便可实现。比如交集、并集、和差集,示例如下:

交集:a & b
并集:a | b
差集:a & (~b)

底层的语言、库、框架常会使用这种方式设置标志位。
以上的例子中只展示了少量数据的处理方式,uint8 占 8 bit 空间,只能表示 8 个数字。那大数据场景能否可以使用这套思路呢?
我们可以把 bitset 和 Go 中的切片结合起来,重新定义 Bits 类型,如下:

type Bitset struct {data []int64
}

但如此也会产生一些问题,设置 bit,我们怎么知道它在哪里呢?仔细想想,这个位置信息包含两部分,即保存该 bit 的数在切片索引位置和该 bit 在数字中的哪位,分别将它们命名为 index 和 position。那怎么获取?
index 可以通过整除获取,比如我们想知道表示 65 的 bit 在切片的哪个 index,通过 65 / 64 即可获得,如果为了高效,也可以用位运算实现,即用移位替换除法,比如 65 >> 6,6 表示移位偏移,即 2^n = 64 的 n。
postion 是除法的余数,我们可以通过模运算获得,比如 65 % 64 = 1,同样为了效率,也有相应的位运算实现,比如 65 & 0b00111111,即 65 & 63。
一个简单例子,如下:

import ("fmt"
)const (shift = 6mask  = 0x3f // 即0b00111111
)type Bitset struct {data []int64
}
func NewBitSet(n int) *Bitset {// 获取位置信息index := n >> shiftset := &Bitset{data: make([]int64, index+1),}// 根据 n 设置 bitsetset.data[index] |= 1 << uint(n&mask)return set
}func (set *Bitset) Contains(n int) bool {// 获取位置信息index := n >> shiftreturn set.data[index]&(1<<uint(n&mask)) != 0
}func main() {set := NewBitSet(65)fmt.Println("set contains 65", set.Contains(65))fmt.Println("set contains 64", set.Contains(64))
}

输出结果
复制代码set contains 65 true
set contains 64 false
以上的例子功能很简单,只是为了演示,只有创建 bitset 和 contains 两个功能,其他诸如添加、删除、不同 bitset 间的交、并、差还没有实现。有兴趣的朋友可以继续尝试。
其实,bitset 包也有人实现了,github地址 bit。可以读读它的源码,实现思路和上面介绍差不多。
下面是一个使用案例。
复制代码package main

import (
“fmt”

"github.com/yourbasic/bit"

)

func main() {
s := bit.New(2, 3, 4, 65, 128)
fmt.Println(“s contains 65”, s.Contains(65))
fmt.Println(“s contains 15”, s.Contains(15))

s.Add(15)
fmt.Println("s contains 15", s.Contains(15))fmt.Println("next 20 is ", s.Next(20))
fmt.Println("prev 20 is ", s.Prev(20))s2 := bit.New(10, 22, 30)s3 := s.Or(s2)
fmt.Println("next 20 is ", s3.Next(20))s3.Visit(func(n int) bool {fmt.Println(n)return false  // 返回 true 表示终止遍历
})

}
执行结果:
复制代码s contains 65 true
s contains 15 false
s contains 15 true
next 20 is 65
prev 20 is 15
next 20 is 22
2
3
4
10
15
22
30
65
128
代码的意思很好理解,就是一些增删改查和集合的操作。要注意的是,bitset 和前面的 set 的区别,bitset 的成员只能是 int 整型,没有 set 灵活。平时的使用场景也比较少,主要用在对效率和存储空间要求较高的场景。

总结

本文介绍了Go 中两种 set 的实现原理,并在此基础介绍了对应于它们的两个包简单使用。我觉得,通过这篇文章,Go 中 set 的使用,基本都可以搞定了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/124561.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES6带来那些js新特性?

ECMAScript 6&#xff08;ES6&#xff09;&#xff0c;也称为 ECMAScript 2015&#xff0c;引入了许多重大的改进和新特性&#xff0c;以改善JavaScript语言的功能和可读性。以下是一些ES6中的主要改变和新特性&#xff1a; 1、let 和 const 声明: 引入了 let 和 const 关键字…

在nodejs中使用Mongoose和MongoDB实现curd操作

在nodejs中使用Mongoose和MongoDB实现curd操作 在Node.js中&#xff0c;数据库被用来存储和检索Web应用程序的数据。它们是构建动态和可伸缩应用程序的重要组成部分。Node.js提供了各种模块和包,可以与数据库一起工作,如MySQL、PostgreSQL、MongoDB等。它们允许开发人员使用各…

50元买来的iPhone手机刷机经验

前段时间&#xff0c;家里的iPad被家人误操作&#xff0c;导致iPad变成不可使用状态。自己折腾了半天&#xff0c;没有找到解决办法。没有办法&#xff0c;只好拿到手机维修店去修理,很快就修理好了.其实也很简单--就是对iPad进行了刷机操作。当然我也看到了刷机的方法。今天&a…

k8s修改apiserver证书可用年限

使用 kubeadm 部署的 K8S 集群中&#xff0c;apiserver 证书的默认可用年限只有一年。如果直接用在生产环境&#xff0c;当证书过期后会造成 K8S 集群瘫痪&#xff0c;从而影响现网业务。 1&#xff0c;查看 K8S 集群所有证书存放位置 ls /etc/kubernetes/pki/ apiserver.crt…

完美的代价

题目&#xff1a; * 题目&#xff1a; * 回文串&#xff0c;是一种特殊的字符串&#xff0c;它从左往右和从右往左读是一样的。 * 现在给你一个串&#xff0c;它不一定是回文的&#xff0c;请你计算最少的交换次数使得该串变为完美的回文回文串。 * 例如&#xff1a;mamad * 第…

微软Azure文本转音频,保存成MP3文件【代码python3】

标签&#xff1a; 文本转音频并保存mp3文件&#xff1b; 微软Azure&#xff1b; 微软Azure可以将文本转音频&#xff0c;并保存mp3文件&#xff0c;直接上代码 代码格式&#xff1a;python 3 import os import azure.cognitiveservices.speech as speechsdk# This example re…

叛乱沙漠风暴server安装 ubuntu 22.04

最新版沙暴已经不支持centos了&#xff0c;还是使用ubuntu比较顺利 官方文档&#xff1a; https://sandstorm-support.newworldinteractive.com/hc/en-us/articles/360049211072-Server-Admin-Guide // 安装steamcmd依赖 sudo add-apt-repository multiverse sudo apt inst…

音视频技术开发周刊 | 317

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 MIT惊人再证大语言模型是世界模型&#xff01;LLM能分清真理和谎言&#xff0c;还能被人类洗脑 MIT等学者的「世界模型」第二弹来了&#xff01;这次&#xff0c;他们证明…

275. H 指数 II

275. H 指数 II 难度: 中等 来源: 每日一题 2023.10.30 给你一个整数数组 citations &#xff0c;其中 citations[i] 表示研究者的第 i 篇论文被引用的次数&#xff0c;citations 已经按照 升序排列 。计算并返回该研究者的 h 指数。 h 指数的定义&#xff1a;h 代表“高…

“探索Linux世界:从CentOS安装到常见命令使用“

目录 引言一、安装CentOS二、Linux的常见命令文件夹和目录操作命令文件编辑命令vi或vim编辑器命令模式编辑模式末行模式 总结 引言 在计算机领域&#xff0c;Linux作为一种强大而灵活的操作系统&#xff0c;在服务器、嵌入式设备和个人电脑等领域广泛应用。本文将引导您了解并…

git cherry-pick命令详细用法

git cherry-pick命令详细用法 1.需求背景2.git cherry-pick介绍3.演示操作4.cherry-pick 支持一次转移多个提交5.代码冲突 1.需求背景 需要在某个稳定版本上&#xff0c;添加一个刚开发完成的版本中的功能。就可以使用 Cherry-pick 命令&#xff0c;将这个功能相关的 commit 提…

unboundlocalerror: local variable ‘××ב referenced before assignment

发现我的代码 if self.flag valid:us self.user_valid_list[idx] elif self.flag test:us self.user_test_list[idx]info sample(us)如果我的flag不是train和valid中的值&#xff0c;那么就会出现问题&#xff0c;因此再加上一个else处理这种情况 if self.flag valid:u…

Linux基础环境开发工具的使用(yum,vim,gcc,g++)

Linux基础环境开发工具的使用[yum,vim,gcc,g] 一.yum1.yum的快速入门1.yum安装软件2.yum卸载软件 2.yum的生态环境1.操作系统的分化2.四个问题1.服务器是谁提供的呢?2.服务器上的软件是谁提供的呢?3.为什么要提供呢?4.yum是如何得知目标服务器的地址和下载链接呢?5.软件源 …

记一次天池参赛总结

第一次参加这类的算法比赛&#xff0c;记录一下自己遇到的一些点&#xff0c;做个总结。 比较浅显的一些记录&#xff0c;第一次的经验之谈&#xff0c;适合首次参加可能容易遇到的问题 文章目录 平台代码tips整理 加载权重文件autopel下载上传 平台 使用的autodl平台 下载大…

基于单片机的空气质量检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 技术交流认准下方 CSDN 官方提供的联系方式 文章目录 概要 一、主要内容二、系统方案设计2.1 系统方案设计2.2 主控制器模块选择 三、 系统软件设计4.1 程序结构分析4.2系统程序…

小红书平台用户数据分析与可视化

管理器、网页下载器、网页解析器、输出管理器这四个模块去搭建一个爬虫框架&#xff0c;将爬虫流程统一化&#xff0c;将通用的功能进行抽象&#xff0c;减少重复工作。要求实现的爬虫框架可以进行分布式爬取&#xff0c;解决爬虫的统一调度和统一去重&#xff0c;以及存储问题…

[激光原理与应用-72]:PLC架构与工作原理

目录 一、PLC简介 1.1 概述 1.2 基本组成 1.3 常见的PLC品牌比较 二、PLC程序执行原理 2.1 PLC有操作系统吗&#xff1f; 2.2 PLC程序执行 2.3 PLC编程语言 2.4 PLC编程过程 三、PLC编程工具 3.1 编程工具 四、PLC与工控机协同 4.1 PLC需要配置工控机吗&#xff1…

SpringBoot / Vue 对SSE的基本使用

一、SSE是什么&#xff1f; SSE技术是基于单工通信模式&#xff0c;只是单纯的客户端向服务端发送请求&#xff0c;服务端不会主动发送给客户端。服务端采取的策略是抓住这个请求不放&#xff0c;等数据更新的时候才返回给客户端&#xff0c;当客户端接收到消息后&#xff0c;再…

MATLAB算法实战应用案例精讲-【图像处理】相机标定(补充篇)

目录 前言 知识储备 摄像头基础知识 一、摄像头结构和工作原理 二、相关参