如何实现一个 Go 语言的字符串切片反转函数

简介: Go 语言不像其他语言如 Python,有着内置的 reverse() 函数,先来看一下 Python 中对于列表的反转方法,然后我们再来学习如果在 Go 语言中实现相同的功能。

Python 中的 reverse 函数

Go 语言不像其他语言如 Python,有着内置的 reverse() 函数,先来看一下 Python 中对于列表的反转方法,然后我们再来学习如果在 Go 语言中实现相同的功能。

>>> myList = [2022, 2021, 2008, 2012]
>>> myList.reverse()
>>> print("Reversed List:", myList)
Reversed List: [2012, 2008, 2021, 2022]
>>> 

实现一个 reverse 反转函数

reverse 算法取一个数据集,并将该数据集的值进行反转,Go 标准的 sort 包并没有内置的方法来反转一个切片。

利用两个切片实现

设计思想:


  1. 确定切片长度
  2. 获取最后一个元素
  3. 以相反的顺序在新切片中添加最后一个元素到第一个位置
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)

前后两两原地交换

我们可以写一个简易的 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() 函数。

反转为原切片的副本

如果我们要返回切片的反转的副本,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
}


运行结果图如下:


image.png

总结

本文通过 Python 中的 reverse() 函数的一个示例,引发出一个思考:Go 语言中有没有类似的反转函数?


然后通过几种方式实现同样的字符串切片的反转功能,并通过借助额外空间和就地反转两种方式实现了功能相同 reverse 函数,其实类似的反转思想也可以用于字符串或者链表反转等其他数据结构。

相关文章
|
4天前
|
JSON 测试技术 Go
零值在go语言和初始化数据
【7月更文挑战第10天】本文介绍在Go语言中如何初始化数据,未初始化的变量会有对应的零值:bool为`false`,int为`0`,byte和string为空,pointer、function、interface及channel为`nil`,slice和map也为`nil`。。本文档作为指南,帮助理解Go的数据结构和正确使用它们。
52 22
零值在go语言和初始化数据
|
4天前
|
存储 Go
go语言中fmt格式化包和内置函数汇总
【7月更文挑战第10天】本文介绍fmt包和`Errorf`用于创建格式化的错误消息。`fmt`包还涉及一些接口,如`Formatter`、`GoStringer`、`ScanState`、`Scanner`和`Stringer`,支持自定义格式化和输入/输出处理。
16 1
|
4天前
|
Go
go语言中格式化输出的占位符
【7月更文挑战第10天】`fmt` 包在 Go 语言中用于格式化输出,包括不同类型的占位符:%v(默认格式)、%+v(带字段名的结构体)、%#v(Go语法表示)、%T(类型表示)、%%(百分号)。布尔值用%t,整数有%b、%c、%d、%o、%q、%x、%X和%U。浮点数和复数用%b、%e、%E、%f、%g、%G。字符串和字节切片用%s、%q、%x、%X。指针用%p。占位符可配合+、-、#、空格和0进行调整。宽度和精度控制输出格式,例如 %.4g 控制小数精度。Go 没有 `%u`,但无符号整数默认打印为正数。运算符包括逻辑、比较、加减、乘除、移位、按位和按位异或等。
17 1
|
2天前
|
安全 Go
Go语言map并发安全,互斥锁和读写锁谁更优?
Go并发编程中,`sync.Mutex`提供独占访问,适合读写操作均衡或写操作频繁的场景;`sync.RWMutex`允许多个读取者并行,适用于读多写少的情况。明智选择锁可提升程序性能和稳定性。示例展示了如何在操作map时使用这两种锁。
4 0
|
2天前
|
安全 Go 开发者
Go语言map并发安全使用的正确姿势
在Go并发编程中,由于普通map不是线程安全的,多goroutine访问可能导致数据竞态。为保证安全,可使用`sync.Mutex`封装map或使用从Go 1.9开始提供的`sync.Map`。前者通过加锁手动同步,后者内置并发控制,适用于多goroutine共享。选择哪种取决于具体场景和性能需求。
5 0
|
2天前
|
存储 安全 Java
Go语言中的map为什么默认不是并发安全的?
Go语言的map默认不保证并发安全,以优化性能和简洁性。官方建议在需要时使用`sync.Mutex`保证安全。从Go 1.6起,并发读写map会导致程序崩溃,鼓励开发者显式处理并发问题。这样做的哲学是让代码更清晰,并避免不必要的性能开销。
4 0
|
Go 索引
golang 字符串操作实例
package main import s "strings" import "fmt" var p = fmt.Println func main() { p("Contains: ", s.
888 0
|
6天前
|
安全 算法 程序员
在go语言中使用泛型和反射
【7月更文挑战第8天】本文介绍go支持泛型后,提升了代码复用,如操作切片、映射、通道的函数,以及自定义数据结构。 泛型适用于通用数据结构和函数,减少接口使用和类型断言。
66 1
在go语言中使用泛型和反射
|
8天前
|
缓存 编译器 Shell
回顾go语言基础中一些特别的概念
【7月更文挑战第6天】本文介绍Go语言基础涵盖包声明、导入、函数、变量、语句和表达式以及注释。零值可用类型如切片、互斥锁和缓冲,支持预分配容量以优化性能。
40 2
回顾go语言基础中一些特别的概念