zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Go 切片转集合(Slice to Set)

2023-02-18 16:27:31 时间

文章目录

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]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 := cast.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 := cast.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 := cast.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([]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](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 := cast.ToBoolE(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case int:
		v, err := cast.ToIntE(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case int8:
		v, err := cast.ToInt8E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case int16:
		v, err := cast.ToInt16E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case int32:
		v, err := cast.ToInt32E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case int64:
		v, err := cast.ToInt64E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case uint:
		v, err := cast.ToUintE(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case uint8:
		v, err := cast.ToUint8E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case uint16:
		v, err := cast.ToUint16E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case uint32:
		v, err := cast.ToUint32E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case uint64:
		v, err := cast.ToUint64E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case float32:
		v, err := cast.ToFloat32E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case float64:
		v, err := cast.ToFloat64E(i)
		if err != nil {
			return t, err
		}
		t = any(v).(T)
	case string:
		v, err := cast.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.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 := []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.ToSetG[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