在使用LeetCode在线平台刷题的时候,可选的debug方法大概有以下几种方式:1. “print”大法,2. playground,3. 调试器(断点调试)。“print”大法是最常见,使用最方便,但是效率也是三种方式中最低的一种。在使用过程中需要不断增加,注释和解注释。playground则对热门语言提供了一些常用的工具函数,但是仍然只能使用“print”大法调试。调试器则属于会员功能,应该为普通断点调试器,且不支持以下语言:C#, Ruby, Swift,
Go, Scala, Kotlin, Rust, PHP, Typescript, Racket。
本文将介绍如何通过Golang的反射机制实现本地的LeetCode调试器。
经过综合对比三种在线debug方法,本地的代码debugger需要具有以下功能:
断点调试
根据文本生成测试用例
常用的工具函数
断点调试只需要使用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]]
跟据文本生成测试用例
读取输入输出
- 通过反射获取输入输出个数
func twoSum(nums []int, target int) []int {}
function := twoSum
fv := reflect.ValueOf(function).Type()
numIn, numOut := fv.NumIn(), fv.NumOut()
所以这里可以得出输入参数个数numIn=2
,返回值个数numOut=1
。
- 根据输入输出个数读取输入输出
由于在input.txt
中同一测试样例的输入参数用\n
隔开,不同测试样例间也没有标识隔开。输出也是。
func readInput(numIn int) [][]string {}
func readOutput(numOut int) [][]string {}
对于这两个函数,都是根据个数读取input.txt
或output.txt
。并返回[][]stirng
格式的args的数组。
通过反射调用函数
- 使用interface和input经过反射调用函数
func Call(function interface{}, args ...string) (output []string) {}
- 使用反射获取每个输入的数据类型
// 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。
- 根据数据类型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
语句。
参考
有疑问加站长微信联系(非本文作者)