生成全排列---reflect包的一个应用

生成全排列---一个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. 输入的slicenil

  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

 

     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值