38

从插入排序来理解 Go 的接口

 4 years ago
source link: https://studygolang.com/articles/25102
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

根据插入排序的思想,我们很容易就可以完成插入排序的代码如下。

func insertionSort(data []int) {
    lo, hi := 0, len(data) - 1
    for i := lo + 1; i < hi; i++ {
        for j := i; j > lo && data[j] < data[j-1]; j-- {
            data[j], data[j-1] = data[j-1], data[j]
        }
    }
}
复制代码

我们可以验证一下,确实没有问题。

package main

import (
    "fmt"
)

func main()  {
    nums := []int{2, 3, 4, 1, 7, 9, 10, 21, 17}
    insertionSort(nums)
    fmt.Println(nums)
}
复制代码

代码输出为,结果正确

[1 2 3 4 7 9 10 17 21]
复制代码

问题

好,现在问题来了,都知道 Go 是静态语言,那么就意味着不同的数据类型可能导致上述的插入排序不可用。比如说,某天产品想要支持 uint32 的插入排序。嗯,很简单,直接 Ctrl+c + Ctrl+c 稍微修改一下。

func insertionSortUint32(data []uint32) {
    lo, hi := 0, len(data) - 1
    for i := lo + 1; i < hi; i++ {
        for j := i; j > lo && data[j] < data[j-1]; j-- {
            data[j], data[j-1] = data[j-1], data[j]
        }
    }
}
复制代码

谁知道哪天产品脑子又抽风,他想要支持 float32 类型的插入排序,代码可能又得加几行。

func insertionSortFloat32(data []float32) {
    lo, hi := 0, len(data) - 1
    for i := lo + 1; i < hi; i++ {
        for j := i; j > lo && data[j] < data[j-1]; j-- {
            data[j], data[j-1] = data[j-1], data[j]
        }
    }
}
复制代码

好像还看得下去,我们知道 Go 中的类型可不止这 3 种,再这么被搞下去是不是要爆炸了?没关系,我们有强大的 IDE 可以快速实现。:smirk::smirk::smirk:

fIB3aaA.gif

好了,开个玩笑。如果我们是提供一个库的形式,使用者需要一个类型,我们就得加一个类型支持,这样就没法搞事了:joy:

解决

首先,回到上诉的三个类型的排序中来,我们可以发现这几个排序除了数据类型是基本一致的。如果我们想用一个函数来支持所有的数据类型,我们是不是只能使用 interface 来实现这个功能?但是 interface 又不支持运算操作,如果断言出来,还是跟以前一样麻烦。我们看看代码中需要对数据进行运算操作的地方。

qmiumm3.png!web

发现排序中只有 len(data)data[j] < data[j-1]data[j], data[j-1] = data[j-1], data[j] 这三种操作 interface 不支持。如果我们让 interface 实现这三个方法不就解决了我的问题了吗?接下来我们通过这种思路修改一下我们的插入排序 。代码如下,

type Data interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

func insertionSort(data Data) {
    lo, hi := 0, data.Len() - 1
    for i := lo + 1; i < hi; i++ {
        for j := i; j > lo && data.Less(j, j-1); j-- {
            data.Swap(j, j-1)
        }
    }
}
复制代码

我们使用了 interface 来替代写死的数据类型。如果调用方使用,只要实现 Data 接口就行了。

package main

import (
    "fmt"
)

type Uint32Slice []uint32

func (u Uint32Slice) Len() int {return len(u)}
func (u Uint32Slice) Less(i, j int) bool {return u[i] < u[j]}
func (u Uint32Slice) Swap(i, j int) {u[i], u[j] = u[j], u[i]}

type Float32Slice []float32

func (u Float32Slice) Len() int {return len(u)}
func (u Float32Slice) Less(i, j int) bool {return u[i] < u[j]}
func (u Float32Slice) Swap(i, j int) {u[i], u[j] = u[j], u[i]}


func main()  {
    nums := Uint32Slice{2, 3, 4, 1, 7, 9, 10, 21, 17}
    insertionSort(nums)
    fmt.Println(nums)

    float32Nums := Float32Slice{2, 3, 4, 1, 7, 9, 10, 21, 17}
    insertionSort(float32Nums)
    fmt.Println(float32Nums)
}
复制代码

可以验证,结果没有问题。

[1 2 3 4 7 9 10 21 17]
[1 2 3 4 7 9 10 21 17]
复制代码

总结

我们通过接口实现了一个支持多种数据类型的插入排序,调用者只需要实现 Data 这个接口就可以使用了,而不用去修改插入排序原有的函数定义。这样使得我们的代码抽象度更高也更灵活,当我们面临类似的需求时,接口就是答案。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK