Go 切片转集合(Slice to Set)

Posted 恋喵大鲤鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go 切片转集合(Slice to Set)相关的知识,希望对你有一定的参考价值。

文章目录

1.Golang 的 Set 类型是什么

我们都知道 Golang 没有集合(Set)类型,为何如此设计呢?

因为 Golang 是一门追求简单、现代、易于使用的语言,所以不会引入不必要的特性。

Set 就是一个例子,Golang 有了 Map,当 Map 中的 value 为空结构体 struct 时,其不就是一个集合(Set)么,所以不用再单独引入一个 Set 类型。

2.切片转集合(Slice to Set)

有了集合,在某些场景下,我们可能需要完成切片到集合类型的转换。

Golang 中,利用反射,我们可以将任意类型的切片或数组转换为对应类型的集合。

// toSetE converts a slice or array to map[any]struct and returns an error if occurred.
func toSetE(i any) (any, error) 
	// Check params.
	if i == nil 
		return nil, fmt.Errorf("the input i is nil")
	
	t := reflect.TypeOf(i)
	kind := t.Kind()
	if kind != reflect.Slice && kind != reflect.Array 
		return nil, fmt.Errorf("the input %#v of type %T isn't a slice or array", i, i)
	

	// Execute the conversion.
	v := reflect.ValueOf(i)
	mT := reflect.MapOf(t.Elem(), reflect.TypeOf(struct))
	mV := reflect.MakeMapWithSize(mT, v.Len())
	for j := 0; j < v.Len(); j++ 
		mV.SetMapIndex(v.Index(j), reflect.ValueOf(struct))
	
	return mV.Interface(), nil

在上面函数的基础上,我们可以封装我们想要的不同类型的转换函数。

转换成 int 集合。

// ToIntSet converts a slice or array to map[int]struct.
func ToIntSet(i any) map[int]struct 
	m, _ := ToIntSetE(i)
	return m


// ToIntSetE converts a slice or array to map[int]structSet with error.
func ToIntSetE(i any) (map[int]struct, error) 
	m, err := toSetE(i)
	if err != nil 
		return nil, err
	
	if v, ok := m.(map[int]struct); ok 
		return v, nil
	
	dst := make(map[int]struct, reflect.ValueOf(m).Len())
	for _, k := range reflect.ValueOf(m).MapKeys() 
		v, err := conv.ToIntE(k.Interface())
		if err != nil 
			return nil, err
		
		dst[v] = struct
	
	return dst, nil

转换成 float64 集合。

// ToFloat64Set converts a slice or array to map[float64]struct.
func ToFloat64Set(i any) map[float64]struct 
	m, _ := ToFloat64SetE(i)
	return m


// ToFloat64SetE converts a slice or array to map[float64]struct and returns an error if occurred.
func ToFloat64SetE(i any) (map[float64]struct, error) 
	m, err := toSetE(i)
	if err != nil 
		return nil, err
	
	if v, ok := m.(map[float64]struct); ok 
		return v, nil
	
	dst := make(map[float64]struct, reflect.ValueOf(m).Len())
	for _, k := range reflect.ValueOf(m).MapKeys() 
		v, err := conv.ToFloat64E(k.Interface())
		if err != nil 
			return nil, err
		
		dst[v] = struct
	
	return dst, nil

转换成 string 集合。

// ToStrSet converts a slice or array to map[string]struct.
func ToStrSet(i any) map[string]struct 
	m, _ := ToStrSetE(i)
	return m


// ToStrSetE converts a slice or array to map[string]struct and returns an error if occurred.
func ToStrSetE(i any) (map[string]struct, error) 
	m, err := toSetE(i)
	if err != nil 
		return nil, err
	
	if v, ok := m.(map[string]struct); ok 
		return v, nil
	
	dst := make(map[string]struct, reflect.ValueOf(m).Len())
	for _, k := range reflect.ValueOf(m).MapKeys() 
		v, err := conv.ToStringE(k.Interface())
		if err != nil 
			return nil, err
		
		dst[v] = struct
	
	return dst, nil

转换示例:

package main

import (
	"fmt"
	"reflect"

	"github.com/spf13/cast"
)

func main() 
	intSet, err := ToIntSetE([]int1, 2, 3)
	fmt.Println(intSet, err)

	f64Set, err := ToFloat64SetE([]float641.1, 2.2, 3.3)
	fmt.Println(f64Set, err)

	strSet, err := ToStrSetE([]string"foo", "bar", "baz")
	fmt.Println(strSet, err)

运行输出:

map[1: 2: 3:] <nil>
map[1.1: 2.2: 3.3:] <nil>
map[bar: baz: foo:] <nil>

3.泛型

Golang 在 1.18 中引入了千呼万唤的泛型,利用泛型,我们可以不用针对具体类型单独封装,少写上面很多重复的代码。

// ToSet converts a slice or array to map[T]struct and returns a nil if error occurred.
func ToSet[T comparable](i any) map[T]struct 
	m, _ := ToSetE[T](i)
	return m


// ToSetE converts a slice or array to map[T]struct and returns an error if occurred.
// Note that the the element type of input don't need to be equal to the map key type.
// For example, []uint641, 2, 3 can be converted to map[uint64]struct1:struct, 2:struct,3:struct
// and also can be converted to map[string]struct"1":struct, "2":struct, "3":struct
// if you want.
// Note that this function is implemented through 1.18 generics, so the element type needs to
// be specified when calling it, e.g. ToSetE[int]([]int1,2,3).
func ToSetE[T comparable](i any) (map[T]struct, error) 
	// Check params.
	if i == nil 
		return nil, fmt.Errorf("the input i is nil")
	
	t := reflect.TypeOf(i)
	kind := t.Kind()
	if kind != reflect.Slice && kind != reflect.Array 
		return nil, fmt.Errorf("the type %T of input %#v isn't a slice or array", i, i)
	

	// Execute the conversion.
	v := reflect.ValueOf(i)
	mapT := reflect.MapOf(t.Elem(), reflect.TypeOf(struct))
	mapV := reflect.MakeMapWithSize(mapT, v.Len())
	for j := 0; j < v.Len(); j++ 
		mapV.SetMapIndex(v.Index(j), reflect.ValueOf(struct))
	
	if v, ok := mapV.Interface().(map[T]struct); ok 
		return v, nil
	
	// Convert the element to the T.
	set := make(map[T]struct, v.Len())
	for _, k := range mapV.MapKeys() 
		v, err := ToAnyE[T](k.Interface())
		if err != nil 
			return nil, err
		
		set[v] = struct
	
	return set, nil

注意: 如果目标集合的类型和切片或数组元素类型不一致,会尝试进行类型转换。转换用到的函数如下:

// ToAnyE converts one type to another and returns an error if occurred.
func ToAnyE[T any](i any) (T, error) 
	var t T
	switch any(t).(type) 
	case bool:
		v, err := conv.ToBoolE(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case int:
		v, err := conv.ToIntE(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case int8:
		v, err := conv.ToInt8E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case int16:
		v, err := conv.ToInt16E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case int32:
		v, err := conv.ToInt32E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case int64:
		v, err := conv.ToInt64E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case uint:
		v, err := conv.ToUintE(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case uint8:
		v, err := conv.ToUint8E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case uint16:
		v, err := conv.ToUint16E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case uint32:
		v, err := conv.ToUint32E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case uint64:
		v, err := conv.ToUint64E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case float32:
		v, err := conv.ToFloat32E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case float64:
		v, err := conv.ToFloat64E(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	case string:
		v, err := conv.ToStringE(i)
		if err != nil 
			return t, err
		
		t = any(v).(T)
	default:
		return t, fmt.Errorf("the type %T is not supported", t)
	
	return t, nil

转换示例:

func main() 
	intSet, err := ToSetE[int]([]int1, 2, 3)
	fmt.Println(intSet, err)

	f64Set, err := ToSetE[float64]([]float641.1, 2.2, 3.3)
	fmt.Println(f64Set, err)

	strSet, err := ToSetE[string]([]string"foo", "bar", "baz")
	fmt.Println(strSet, err)

运行输出:

map[1: 2: 3:] <nil>
map[1: 2: 3:] <nil>
map[bar: baz: foo:] <nil>

4.go-huge-util

为了方便大家使用,以上相关代码已开源至 Github 工具库 go-huge-util,大家可使用 go mod 方式 import 然后使用。

import (
    "github.com/dablelv/go-huge-util/conv"
)

// Convert bool slice or array to set.
bools := []booltrue, false, true
set := conv.ToBoolSet(bools)
set, _ := conv.ToBoolSetE(bools)
set := conv.ToSet[bool](bools)
set, _ := conv.ToSetE[bool](bools)

// Convert int slice or array to set.
ints := []int1, 2, 3
set := conv.ToIntSet(ints)
set, _ := conv.ToIntSetE(ints)
set := conv.ToSet[int](ints)
set, _ := conv.ToSetE[int](ints)

// Convert string slice or array to set.
strs := []string"foo", "bar", "baz"
set := conv.ToStrSet(strs)
set, _ := conv.ToStrSetE(strs)
set := conv.ToSet[string](strs)
set, _ := conv.ToSetE[string](strs)

// Convert int8, int16, int32, uint etc. slice or array to set.
// ...

参考文献

github.com/dablelv/go-huge-util

以上是关于Go 切片转集合(Slice to Set)的主要内容,如果未能解决你的问题,请参考以下文章

吴裕雄--天生自然--Go 语言学习笔记--Go 语言切片(Slice)

Go语言切片

GO语言切片Slice数组Array的比较;利用多维切片接受EXCEL内容的输出

GO语言学习系列六——GO的数组 array与切片 slice

转go里面字符串转成 字节slice, 字节slice转成字符串

go语言中实现切片(slice)的三种方式