利用Golang反射机制(Reflect)搭建本地LeetCode调试器

一口闰心 · · 416 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在使用LeetCode在线平台刷题的时候,可选的debug方法大概有以下几种方式:1. “print”大法,2. playground,3. 调试器(断点调试)。“print”大法是最常见,使用最方便,但是效率也是三种方式中最低的一种。在使用过程中需要不断增加,注释和解注释。playground则对热门语言提供了一些常用的工具函数,但是仍然只能使用“print”大法调试。调试器则属于会员功能,应该为普通断点调试器,且不支持以下语言:C#, Ruby, Swift,

Go, Scala, Kotlin, Rust, PHP, Typescript, Racket。

本文将介绍如何通过Golang的反射机制实现本地的LeetCode调试器。

经过综合对比三种在线debug方法,本地的代码debugger需要具有以下功能:

  1. 断点调试

  2. 根据文本生成测试用例

  3. 常用的工具函数

断点调试只需要使用Goland或VSCode的debug运行即可,常用的工具函数也是日积月累的过程。所以这里需要实现的其实是LeetCode端很不起眼,但是很影响效率的功能:根据文本生成测试用例。如果没有这个功能,在对本地代码进行测试时,需要把每个测试用例根据对应数据类型硬编码。所以本文将专注介绍如何实现这个功能。

这里用leet-code第一题twoSum作为例子:

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

答案:

func twoSum(nums []int, target int) []int {
   for i := 0; i < len(nums); i++ {
       for j := i+1; j < len(nums); j++ {
           if nums[i] + nums[j] == target {
               return []int{i,j}
           }
       }
   }
   return []int{-1,-1}
}

main函数


func main() {

    var function interface{}

    // Replace with your function name

    // function = f1

    function = twoSum

    // Step 1: use reflect to get the # of input parameters and # of return (output) value

    fv := reflect.ValueOf(function).Type()

    numIn, numOut := fv.NumIn(), fv.NumOut()

    // Step 2: read input from input.txt according to the # of input parameters

    inputs := readInput(numIn)

    // Step 3: read output from output.txt according to the # of output parameters

    expects := readOutput(numOut)

    for len(inputs) > len(expects) {

        // Step 4: we allow not specifing the output

        expects = append(expects, []string{})

    }

    // Iterate all the inputs

    for i := 0; i < len(inputs); i++ {

        input := inputs[i]

        expect := expects[i]

        // Step 5: call the solution function with given input args

        output := utils.Call(function, input...)

        if equal(expect, output) {

            fmt.Println("Pass test case", i)

        } else if len(expect) == 0 {

            fmt.Printf("Input: \t%s\nGot: \t%s\n", input, output)

        } else {

            fmt.Println("Fail test case", i)

            fmt.Printf("Expected:\t%s\nGot:\t\t%s\n", expect, output)

        }

    }

}

input.txt:


[2,7,11,15]

9

[3,2,4]

6

[3,3]

6

output.txt:


[0,1]

[1,2]

[0,1]

stdout


Pass test case 0

Pass test case 1

Pass test case 2

---if output.txt is empty---

Input:  [[2,7,11,15] 9]

Got:    [[0,1]]

Input:  [[3,2,4] 6]

Got:    [[1,2]]

Input:  [[3,3] 6]

Got:    [[0,1]]

跟据文本生成测试用例

读取输入输出

  1. 通过反射获取输入输出个数

func twoSum(nums []int, target int) []int {}



function := twoSum

fv := reflect.ValueOf(function).Type()

numIn, numOut := fv.NumIn(), fv.NumOut()

所以这里可以得出输入参数个数numIn=2,返回值个数numOut=1

  1. 根据输入输出个数读取输入输出

由于在input.txt中同一测试样例的输入参数用\n隔开,不同测试样例间也没有标识隔开。输出也是。


func readInput(numIn int) [][]string {}

func readOutput(numOut int) [][]string {}

对于这两个函数,都是根据个数读取input.txtoutput.txt。并返回[][]stirng格式的args的数组。

通过反射调用函数

  1. 使用interface和input经过反射调用函数

func Call(function interface{}, args ...string) (output []string) {}

  1. 使用反射获取每个输入的数据类型

// func Call()

value := reflect.ValueOf(function)

if value.Kind() != reflect.Func {

    log.Println("function is not function")

    return

}



parameters := make([]reflect.Type, 0, value.Type().NumIn())

for i := 0; i < value.Type().NumIn(); i++ {

    arg := value.Type().In(i)

    parameters = append(parameters, arg)

}

这里的parameters就记录了每一个输入参数的type。

  1. 根据数据类型parse对应输入

// func Call()

argValues := make([]reflect.Value, 0, len(parameters))

for i := 0; i < len(args); i++ {

    switch parameters[i] {

        case reflect.TypeOf(0):

            v, err := strconv.ParseInt(args[i], 10, 64)

            if err != nil {

                log.Printf("argument[%d] %s convert int failed, %v \n", i, args[i], err)

                return

            }

            argValues = append(argValues, reflect.ValueOf(int(v)))

        case reflect.TypeOf(""):

            argValues = append(argValues, reflect.ValueOf(args[i][1:len(args[i])-1]))

        case reflect.TypeOf([]int{}):

            arr := structures.ParseIntArr(args[i])

            argValues = append(argValues, reflect.ValueOf(arr))

        case reflect.TypeOf([][]int{}):

            arr := structures.Parse2dIntArr(args[i])

            argValues = append(argValues, reflect.ValueOf(arr))

        default:

            log.Printf("unsupport type %s[%s] \n", parameters[i].Kind(), parameters[i].Name())

          return

    }

}



这里目前只parse了比较常用的输入类型,像链表和树等特殊结构需要新加一个case去处理。对于输出来说,除了特殊结构以外只需要转string,不需要添加很多switch-case语句。

参考


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:一口闰心

查看原文:利用Golang反射机制(Reflect)搭建本地LeetCode调试器

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

416 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传