Go语言中关于set的实现思考分析

 更新时间:2024年01月18日 08:12:16   作者:visforest  
Go 开发过程中有时我们需要集合(set)这种容器,但 Go 本身未内置这种数据容器,故常常我们需要自己实现,下面我们就来看看具体有哪些实现方法吧

Go 开发过程中有时我们需要集合(set)这种容器,但 Go 本身未内置这种数据容器,故常常我们需要自己实现,其实实现也很简单。

附,推荐阅读:github.com/Visforest/goset

map[xxx]struct{}

最常用和最容易想到的实现是使用 map,如:

type StrSet struct{
    data map[string]struct{}
}

map 的 value 部分设计为 struct{} 类型是为了节省内存空间。

map[interface{}]struct{}

上面实现的是 string 的 set,如果要其他类型的 set 就得再定义 Int8SetIntSetFloat32Set 等等,很是繁琐。

很多人可能会选择这样实现 :

type Set struct {
	data map[interface{}]struct{}
}

// New creates a new Set
func New(v ...interface{}) *Set {
	s := &Set{data: map[interface{}]struct{}{}}
	for _, ele := range v {
		s.data[ele] = struct{}{}
	}
	return s
}

// ...

// ToList returns data slice
func (s *Set) ToList() []interface{} {
	var data = make([]interface{}, len(s.data))
	var i int
	for d := range s.data {
		data[i] = d
		i++
	}
	return data
}

这种方式有几个问题:

执行如下代码:

func main() {
	var l1 = []int{1, 2, 3}
	var l2 = []int{4, 5, 6}
	var s = NewSet(l1, l2)
	for _, e := range s.ToList() {
		fmt.Println(e)
	}
}

出错:

panic: runtime error: hash of unhashable type []int

原因很简单,[]int 是不能被 hash 计算的,即不能作为 map 的 key,读者可以查阅 map key允许的类型。interface{} 这种“万金油” 也可能是不合适的。

观察下面代码

func main() {
	var s = NewSet("a", "b", "c")
	var tmp []string
	for _, e := range s.ToList() {
		tmp = append(tmp, e.(string))
	}
	test(tmp)
}

test 函数不能直接拿 s.ToList() 作为入参,必须将 s.ToList() 进行转换为 []string,原因不言自明。

每次都要转换明显损失了编码效率和执行效率。

map[T comparable]struct{}

上面的弊端,可以用 泛型(generics)解决。

定义:

type Set[T comparable] struct {
	data map[T]struct{}
}

// New creates a new Set
func NewSet[T comparable](v ...T) *Set[T] {
	s := &Set[T]{data: map[T]struct{}{}}
	for _, ele := range v {
		s.data[ele] = struct{}{}
	}
	return s
}

func (s *Set[T]) Add(v ...T) {
	for _, ele := range v {
		s.data[ele] = struct{}{}
	}
}

// ...

// ToList returns data slice
func (s *Set[T]) ToList() []T {
	var data = make([]T, len(s.data))
	var i int
	for d := range s.data {
		data[i] = d
		i++
	}
	return data
}

使用:

func test1(data []string) {
	// ...
}

func test2(data []float64) {
	// ...
}

func main() {
	var s1 = NewSet("a", "b", "c")
	test1(s1.ToList())

	var s2 = NewSet(1.3, 2.2, 3)
	test2(s2.ToList())
}

type IntSet = Set[int]

上面的 Set 是个通用 set,类型混用时自己可能会被误导。我们可以定义专用数据类型的 set,且代码不需要很多。

type IntSet = Set[int]

func NewIntSet(v ...int) *IntSet {
	return NewSet[int](v...)
}

使用:

func main() {
	var s = NewIntSet(1, 2, 3)
	test3(s.ToList())

	// 编译错误
	// s.Add("a", "b", "c")
}

fifo set

通常 set 是无序的,上面的实现也都是无序的,但有的场景下我们需要有序的 set,比如fifo set,sorted set。这里以 fifo set 为例,讨论下其实现。

为了兼顾查找效率和有序特性,可以使用 map + array / double linkedlist,考虑到数据的添加、删除以及内存使用,double linkedlist 有比 array 显著的优势。

type setNode[T comparable] struct {
	val  T
	pre  *setNode[T]
	next *setNode[T]
}

type FifoSet[T comparable] struct {
	head *setNode[T]
	tail *setNode[T]
	data map[T]*setNode[T]
}

// add data, make it first in first out
func (l *FifoSet[T]) Add(v ...T) {
	if len(v) == 0 {
		return
	}

	var i int
	if l.head == nil {
		// first node
		n := &setNode[T]{
			val: v[i],
		}
		l.head = n
		l.tail = n
		l.data[v[i]] = n
		i++
	}
	for ; i < len(v); i++ {
		if _, ok := l.data[v[i]]; !ok {
            // when missing, insert
			n := &setNode[T]{
				val:  v[i],
				pre:  l.tail,
				next: nil,
			}
			l.tail.next = n
			l.tail = n
			l.data[v[i]] = n
		}
	}
}

使用:

func main() {
	var s = NewFifoSet[string]()
	s.Add("e", "a", "b", "a", "c", "b")
	// e
	// a
    // b
	// c
	for _, v := range s.ToList() {
		fmt.Println(v)
	}
}

sorted set

其实 sorted set 与 fifo set 实现很像,只是略有区别,这里就略过了。

有兴趣的可以阅读笔者的 github.com/Visforest/goset,或者自己尝试自己实现下。

以上就是Go语言中关于set的实现思考分析的详细内容,更多关于Go set的资料请关注脚本之家其它相关文章!

相关文章

  • golang 进度条功能实现示例

    golang 进度条功能实现示例

    这篇文章主要介绍了golang 进度条功能实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Go语言异步API设计的扇入扇出模式详解

    Go语言异步API设计的扇入扇出模式详解

    这篇文章主要为大家介绍了Go语言异步API设计的扇入扇出模式示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • golang实现PHP数组特性的方法

    golang实现PHP数组特性的方法

    我们做业务过程中,对应强类型语言使用有个痛点,就是使用变量之前一定要定义变量类型,那么本文就来介绍一下golang实现PHP数组特性的方法
    2021-12-12
  • goland配置自动注释的实现

    goland配置自动注释的实现

    本文主要介绍了goland配置自动注释的实现,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08
  • 详解Golang中gomock的使用场景和方法

    详解Golang中gomock的使用场景和方法

    gomock是Go编程语言的模拟框架, 它与Go的内置测试包很好地集成在一起,但也可以在其他上下文中使用,本文主要介绍了gomock的使用场景和方法,感兴趣的可以了解下
    2024-10-10
  • GO语言 复合类型专题

    GO语言 复合类型专题

    这篇文章主要介绍了GO语言 复合类型的的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • golang如何修改json文件内容的方法示例

    golang如何修改json文件内容的方法示例

    这篇文章主要介绍了golang如何修改json文件内容的方法示例,使用一个例子说明golang如何访问和修改json文件,有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-10-10
  • Go Web下gin框架的模板渲染的实现

    Go Web下gin框架的模板渲染的实现

    Gin框架是目前非常流行的Go语言Web框架之一,作为一个轻量级的框架,Gin提供了丰富的功能和灵活的架构,本文就来介绍下Go Web下gin框架的模板渲染的实现,感兴趣的可以了解一下
    2023-10-10
  • Go语言的type func()用法详解

    Go语言的type func()用法详解

    在Go语言中,函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句,这篇文章主要介绍了Go语言的type func()用法,需要的朋友可以参考下
    2022-03-03
  • go运算符对变量和值执行操作示例详解

    go运算符对变量和值执行操作示例详解

    这篇文章主要为大家介绍了go运算符对变量和值执行操作示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09

最新评论