生成全排列---一个reflect包的应用
在测试函数时,经常需要生成一个全排列,确保函数可以正确处理所有的输入情况,Go语言的特性以及reflect包的存在,使得在Go语言中实现这样的功能非常容易。
输入:
使用NewPerm()函数来生成一个Permatator
NewPerm(k interfac{],less Less) (*Permutator,error)
我们选择使用slice来作为输入的数据类型,为了扩展适用范围,slice的元素可以为任何的Go类型。由于我们选择了lexical顺序,因此如何提供一个大小比较操作就非常重要。 在这里,我们使用了一个特殊类型Less的参数来完成这一工作。
type Less func(i,j interface{}) bool
Go语言定义了ordered性质,给算术类型和string类型实现了比较操作符,我们可以利用这一特性。如果slice的元素类型实现了ordered特性,那么这个参数可以为nil
检测输入的合法性,以下情况非法
1. 输入不是slice
2. 输入的slice为nil
3. 输入的slice包含0个元素
4. 输入的slice元素未实现ordered特性且less参数为nil
这4种错误,可以使用以下代码检测
v := reflect.ValueOf(k)
//check to see if i is a slice
if v.Kind() != reflect.Slice {
return nil, errors.New("argument must be a slice")
}
if v.IsValid() != true {
return nil, errors.New("argument must not be nil")
}
if v.Len() == 0 {
return nil, errors.New("argument must not be empty")
}
if less == nil {
switch v.Type().Elem().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
less = lessInt
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
less = lessUint
case reflect.Float32, reflect.Float64:
less = lessFloat
case reflect.String:
less = lessString
default:
return nil, errors.New("the element type of slice is not ordered,you must provide a function\n")
}
}
lessxxx()
函数是我们定义的几个函数,包装了
<
操作符,定义非常简单
func lessUint(i, j interface{}) bool {
return reflect.ValueOf(i).Uint() < reflect.ValueOf(j).Uint()
}
func lessInt(i, j interface{}) bool {
return reflect.ValueOf(i).Int() < reflect.ValueOf(j).Int()
}
func lessFloat(i, j interface{}) bool {
return reflect.ValueOf(i).Float() < reflect.ValueOf(j).Float()
}
func lessString(i, j interface{}) bool {
return reflect.ValueOf(i).Interface().(string) < reflect.ValueOf(j).Interface().(string)
}
唯一需要注意的就是lessString()函数了。Value.String()方法并不是返回字符串值,而且输出字符串格式,因此我们需要获得其对应的interface{},然后将其转换为字符串类型。
处理:
处理步骤相当简单,就是用了一个简单的算法
对于slice a[0], a[1], ..., a[n − 1]
1 找到最大的索引k ,满足 a[k] < a[k + 1]. 如果这样的索引不存在,那么所有排列已经输出
2 找到最大的索引 l ,满足a[k] < a[l].
3 交换a[k] 和a[l].的在值
4 逆转从a[k + 1] 到a[n]的元素,两个节点包括在内
这个算法有一个前提,就是slice必须已经是升序排列。我们使用sort.Sort()来完成这一个工作
i:=0
for i = 0; i < length-1; i++ {
if !less(v.Index(i).Interface(), v.Index(i+1).Interface()) {
break
}
}
if i != length-1 {
sort.Sort(sortable{v, less})
}
其中sortable类型是一个自定义类型,用于实现sort.Interface{}接口
type sortable struct {
value reflect.Value
less Less
}
func (s sortable) Len() int {
return s.value.Len()
}
func (s sortable) Less(i, j int) bool {
return s.less(s.value.Index(i).Interface(), s.value.Index(j).Interface())
}
func (s sortable) Swap(i, j int) {
temp := reflect.ValueOf(s.value.Index(i).Interface())
s.value.Index(i).Set(s.value.Index(j))
s.value.Index(j).Set(temp)
}
以上代码中,sortable.Swap()函数值得注意,它的执行过程有点诡异,就是temp变量的初始化。
为什么不能temp:=s.value.Index(i)这样操作呢?
原因很简单,Value类型是一个Go对象值的运行时体现,它并不是一个值。如果我们按照temp:=s.value.Index(i)操作,那么会发生如下事情
1.temp变量是第i个元素的值的运行时体现
2.将第j个元素的的值赋予第i个元素
3.将第temp体现的值,也就是第i个元素的值赋予第j个元素
如果在Swap之前,第i个元素的值为1,第j个元素的值为10,那么Swap完成后,这两个元素的值都是10.
由于Value对象的这一根本特性,如果我们需要保证Permutator对象和其输入、输出完全分离。修改输入输出不会影响Permutator对象,必须将输入复制一份,存放在Permutator内部,然后每次将slice复制一份,用于输出。复制过程很简单,reflect包提供了所有需要的工具
length:=v.Len()
l:= reflect.MakeSlice(v.Type(),length,length)
reflect.Copy(l,p.value)
这里需要注意的是MakeSlice()函数,它的第一个参数是一个slice类型,然后生成一个该类型的slice。也就是说,如果v表示一个[]int,那么输出一个[]int,而不受[][]int.如果需要后者,那么就要这样操作
length:=v.Len()
t:=reflect.SliceOf(v.Type())
l:= reflect.MakeSlice(t,length,length)
总结:
Value对象并不是一个值,它是某个Go对象值的运行时表示,这一点非常重要
源码:
https://github.com/fighterlyt/permutation/blob/master/permutation.go