1.Python 中的 reverse 函数
Go 语言不像其他语言如 Python,有着内置的 reverse() 函数,先来看一下 Python 中对于列表的反转方法,然后我们再来学习如果在 Go 语言中实现相同的功能。
>>> myList = [2022, 2021, 2008, 2012]
>>> myList.reverse()
>>> print("Reversed List:", myList)
Reversed List: [2012, 2008, 2021, 2022]
>>>
2.实现一个 reverse 反转函数
reverse 算法取一个数据集,并将该数据集的值进行反转,Go 标准的 sort 包并没有内置的方法来反转一个切片。
利用两个切片实现
设计思想:
- 确定切片长度
- 获取最后一个元素
- 以相反的顺序在新切片中添加最后一个元素到第一个位置
package main
import "fmt"
func main() {
s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}
// 定义新的反转切片
reverseOfS := []string{}
// 遍历原切片 s
for i := range s {
reverseOfS = append(reverseOfS, s[len(s)-1-i])
}
fmt.Println(reverseOfS)
}
运行结果:
[zzz abc go bar foo hello]
显然,这种方式会额外花费一个相同空间的切片,空间复杂度为 O(n)。
3前后两两原地交换
我们可以写一个简易的 reverse 函数来进行数据的反转,通过循环原切片发热一半,然后依次与对应的元素进行交换,比如::
func reverse(s []string) []string {
for i := 0; i < len(s)/2; i++ {
j := len(s) - i - 1
s[i], s[j] = s[j], s[i]
}
return s
}
这个函数可以通过更简短的实现,通过 Go 内部的操作进行循环:
package main
import "fmt"
func reverse(s []string) []string {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
return s
}
func main() {
s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}
reverseOfS := reverse(s)
fmt.Println(reverseOfS)
}
执行结果:
[zzz abc go bar foo hello]
但是,上面的 reverse 函数都是通过切片按值传递,其实我们在修改传递中的 []string 切片,实际上,可以通过以下方式进一步简写:
package main
import "fmt"
func reverse(s []string) {
for i := 0; i < len(s)/2; i++ {
j := len(s) - i - 1
s[i], s[j] = s[j], s[i]
}
}
func main() {
s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}
reverse(s)
fmt.Printf("%v\n", s)
}
此时,reverse() 函数不会返回切片的另一个引用,此时的交换就是就地进行,此时更像文章开头 Python 中的 reverse() 函数。
4.反转为原切片的副本
如果我们要返回切片的反转的副本,reverse 函数就可以这样写:
package main
import "fmt"
func reverse(s []string) []string {
newS := make([]string, len(s))
for i, j := 0, len(s)-1; i <= j; i, j = i+1, j-1 {
newS[i], newS[j] = s[j], s[i]
}
return newS
}
func main() {
s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}
fmt.Printf("原字符串切片:%v\n", s)
fmt.Printf("反转后的切片:%v\n", reverse(s))
}
运行结果:
原字符串切片:[hello foo bar go abc zzz]
反转后的切片:[zzz abc go bar foo hello]
可以看到,原切片是没有变化的。
当然,因为我们没有就地修改原切片,因此又可以回到最初的方法 append,看代码:
func reverse(s []string) []string {
newS := make([]string, 0, len(s))
for i := len(s)-1; i >= 0; i-- {
newS = append(newS, s[i])
}
return newS
}
运行结果图如下:
5.总结
本文通过 Python 中的 reverse() 函数的一个示例,引发出一个思考:Go 语言中有没有类似的反转函数?
然后通过几种方式实现同样的字符串切片的反转功能,并通过借助额外空间和就地反转两种方式实现了功能相同 reverse 函数,其实类似的反转思想也可以用于字符串或者链表反转等其他数据结构。