golang自定义类型slice去重

方法一

package main

import (
	"fmt"
	"github.com/hyperjumptech/beda"
	"reflect"
	"strings"
)

func main() {
    
    
	//data := []string{"a", "b", "c", "d", "a", "c"}
	//fmt.Printf("%#v\n", DeDuplicate(data).([]string))
	data := []int{
    
    1, 2, 3, 4, 7, 2, 3}
	fmt.Printf("%#v\n", DeDuplicate(data).([]int))
}

// DeDuplicate 切片去重
func DeDuplicate(data interface{
    
    }) interface{
    
    } {
    
    
	inArr := reflect.ValueOf(data)
	if inArr.Kind() != reflect.Slice && inArr.Kind() != reflect.Array {
    
    
		return data
	}

	existMap := make(map[interface{
    
    }]bool)
	outArr := reflect.MakeSlice(inArr.Type(), 0, inArr.Len())

	for i := 0; i < inArr.Len(); i++ {
    
    
		iVal := inArr.Index(i)

		if _, ok := existMap[iVal.Interface()]; !ok {
    
    
			outArr = reflect.Append(outArr, inArr.Index(i))
			existMap[iVal.Interface()] = true
		}
	}

	return outArr.Interface()
}

方法二

package main

import (
	"fmt"
	"reflect"
)

func main() {
    
    
	//data := []string{"a", "b", "c", "d", "a", "c"}
	//fmt.Printf("%#v\n", DeDuplicate(data).([]string))
	//data1 := []int{1, 2, 3, 4, 7, 2, 3}
	//fmt.Printf("%#v\n", DeDuplicate(data1).([]int))

	strArr := []string{
    
    "11", "22", "22", "44", "44", "55"}
	DeduplicateOriginal(&strArr) //注意:是切片的指针
	fmt.Printf("%#v\n", strArr)
}

// DeDuplicate 切片去重
func DeDuplicate(data interface{
    
    }) interface{
    
    } {
    
    
	inArr := reflect.ValueOf(data)
	if inArr.Kind() != reflect.Slice && inArr.Kind() != reflect.Array {
    
    
		return data
	}

	existMap := make(map[interface{
    
    }]bool)
	outArr := reflect.MakeSlice(inArr.Type(), 0, inArr.Len())

	for i := 0; i < inArr.Len(); i++ {
    
    
		iVal := inArr.Index(i)

		if _, ok := existMap[iVal.Interface()]; !ok {
    
    
			outArr = reflect.Append(outArr, inArr.Index(i))
			existMap[iVal.Interface()] = true
		}
	}

	return outArr.Interface()
}

// DeduplicateOriginal 传入的data必须是 指向切片的指针
func DeduplicateOriginal(data interface{
    
    }) {
    
    

	dataVal := reflect.ValueOf(data)
	if dataVal.Kind() != reflect.Ptr {
    
    
		fmt.Println("input data.kind is not pointer")
		return
	}

	tmpData := DeDuplicate(dataVal.Elem().Interface())
	tmpDataVal := reflect.ValueOf(tmpData)

	dataVal.Elem().Set(tmpDataVal)
}

实现思路

具体类型的实现

通用往往是对个例的抽象,或者说是是归纳与演绎两大法宝之归纳法。
以对[]int64的去重为例:

func DedumplicateInt64(data []int64) []int64 {
    
    
    outArr := make([]int64, 0)
    existMap := make(map[int64]bool)

    for _, v := range data {
    
    
        if _, ok := existMap[v]; !ok {
    
    
            outArr = append(outArr, v)
            existMap[v] = true
        }
    }
    return outArr
}

小说明:这里的existMap其实就是充当set的作用。

利用反射实现方式一

怎样将上面的逻辑翻译成对下面通用interface的处理呢?

func Deduplicate(data interface{
    
    }) interface{
    
    } {
    
    }

答案是:反射!

因为interface中保存着 运行时 原数据的类型和值,
而反射的特性用于处理运行时才知道类型的数据再合适不过了。

interface和reflect.Value的互转

  • func ValueOf(i interface{}) Value
    该函数可以获取到Interface{}实际存储的值;
  • func (v Value) Interface() (i interface{})
    该函数可以将实际存储的值转化为interface{};

操作任意类型的slice

  • reflect.MakeSlice
  • reflect.Append

有了上面两个基础知识后,翻译也就水到渠成了。

从方式一到方式二

方式二修改原slice,节约空间的方法是用类似于quicksort的IN-PLACE算法,

但本文主要是探究语言层面的实现,因而对算法的优化有所忽略,
所以这里先调用方案一拿到去重后的结果,再修改原输入的slice。

怎么修改原slice,我还真的卡了好长时间!
注意点:slice的传参,是值传递!
所以要想修改原切片,传给参数的值必须是 指向切片的指针!
reflect.Value的两个重要函数

  • func (v Value) Elem() Value
// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.

所以Elem()相当于*ptr的作用,也就是解引用。

  • func (v Value) Set(x Value)

发散:从另一种实现看slice的内部结构

func DeduplicateOriginal(data interface{
    
    }) {
    
    

    dataVal := reflect.ValueOf(data)
    if dataVal.Kind() != reflect.Ptr {
    
    
        fmt.Println("input data.kind is not pointer")
        return
    }

    tmpData := Deduplicate(dataVal.Elem().Interface())
    tmpDataVal := reflect.ValueOf(tmpData)

    intArrP := (*reflect.SliceHeader)(unsafe.Pointer(dataVal.Pointer()))

    intArrP.Len = tmpDataVal.Len()
    intArrP.Cap = tmpDataVal.Cap()
    intArrP.Data = tmpDataVal.Pointer()
}

上面这种实现也是ok的。

因为slice实际上是下面这个结构:

// SliceHeader is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
    
    
    Data uintptr
    Len  int
    Cap  int
}

猜你喜欢

转载自blog.csdn.net/SweetHeartHuaZai/article/details/130017928