1.什么是 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]struct{Set} 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/dablelv/cyan/conv"
)
func main() {
intSet, err := ToIntSetE([]int{1, 2, 3})
fmt.Println(intSet, err)
f64Set, err := ToFloat64SetE([]float64{1.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, []uint64{1, 2, 3} can be converted to map[uint64]struct{}{1: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]([]int{1,2,3}).
func ToSetE[T comparable](a any) (map[T]struct{}, error) {
// Check input.
if a == nil {
return nil, nil
}
t := reflect.TypeOf(a)
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", a, a)
}
v := reflect.ValueOf(a)
if kind == reflect.Slice && v.IsNil() {
return nil, nil
}
// Execute the conversion.
mapT := reflect.MapOf(t.Elem(), reflect.TypeOf(struct{}{}))
mapV := reflect.MakeMapWithSize(mapT, v.Len())
for i := 0; i < v.Len(); i++ {
mapV.SetMapIndex(v.Index(i), reflect.ValueOf(struct{}{}))
}
if v, ok := mapV.Interface().(map[T]struct{}); ok {
return v, nil
}
// Convert the element to the type 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
}
注意: 如果目标集合的类型和切片或数组元素类型不一致,会尝试进行类型转换。转换用到的函数如下:
import "github.com/dablelv/cyan/conv"
// 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]([]int{1, 2, 3})
fmt.Println(intSet, err)
f64Set, err := ToSetE[float64]([]float64{1.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.dablelv/cyan
为了方便大家使用,以上相关代码已开源至 Github 工具库 dablelv/cyan,可 import 直接使用,欢迎大家 star 和 pr。
import (
"github.com/dablelv/cyan/conv"
)
// Convert bool slice or array to set.
bools := []bool{true, 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 := []int{1, 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.
// ...