ChatGPT解决这个技术问题 Extra ChatGPT

检查两个切片的相等性

鉴于运算符 ==!= 不是一个选项,我如何检查两个切片是否相等?

package main

import "fmt"

func main() {
    s1 := []int{1, 2}
    s2 := []int{1, 2}
    fmt.Println(s1 == s2)
}

这不编译:

无效操作:s1 == s2(slice只能和nil比较)


S
Samuel Liew

您应该使用 reflect.DeepEqual()

DeepEqual 是 Go 的 == 运算符的递归松弛。 DeepEqual 报告 x 和 y 是否“深度相等”,定义如下。如果以下情况之一适用,则两个相同类型的值非常相等。不同类型的值永远不会完全相等。当对应的元素深度相等时,数组值深度相等。如果结构值的对应字段(导出和未导出)深度相等,则结构值深度相等。如果两者都为零,则 Func 值非常相等;否则他们不是完全平等的。如果接口值具有高度相等的具体值,则它们是高度相等的。如果映射值是相同的映射对象,或者它们具有相同的长度并且它们对应的键(使用 Go 相等匹配)映射到深度相等的值,则映射值是深度相等的。如果指针值使用 Go 的 == 运算符相等或者它们指向深度相等的值,则它们是深度相等的。当满足以下所有条件时,切片值是完全相等的:它们都是 nil 或都非 nil,它们具有相同的长度,或者它们指向同一底层数组的相同初始条目(即 &x[0 ] == &y[0]) 或其对应的元素(直到长度)非常相等。请注意,非 nil 空切片和 nil 切片(例如,[]byte{} 和 []byte(nil))并不完全相等。如果使用 Go 的 == 运算符相等,则其他值 - 数字、布尔值、字符串和通道 - 是完全相等的。


一个非常有用的答案。不管反映包的一般性能如何,有一个预先打包的深度相等函数用于简单和正确性至关重要的测试用例是非常好的。
我刚刚运行了一个基准测试,reflect.DeepEqual 比循环慢 150 倍。如果有人想在生产中使用这种方法,仅供参考。
它不会将随机排序的切片与相同的项目进行比较:(
@Hemant_Negi 如果两个切片的顺序不同,它们就不相等。如果要在忽略顺序的情况下比较两个切片的相等性,则对它们进行排序然后检查,或者将项目从一个切片移动到映射中,然后检查另一个切片上的每个元素是否在映射中。 (另外确保它们具有相同的长度)
Rob Pike(2011 年)在 Go 的反思中,在官方 Go 博客中写道:“这是一个强大的工具,除非绝对必要,否则应谨慎使用并避免使用”blog.golang.org/laws-of-reflection。我不会在生产代码中使用反射来比较切片。这是一个很容易编写的函数。但请注意,这个问题的选择答案也存在潜在缺陷,具体取决于您期望的行为:它会发现已初始化但仍处于 len 0 和 cap 0 的切片与已初始化的切片不匹配已声明但未初始化。
I
Inanc Gumus

您需要遍历切片中的每个元素并进行测试。未定义切片的相等性。但是,如果您要比较 []byte 类型的值,则有一个 bytes.Equal 函数。

func testEq(a, b []Type) bool {
    if len(a) != len(b) {
        return false
    }
    for i := range a {
        if a[i] != b[i] {
            return false
        }
    }
    return true
}

建议:for i, v := range a { if v != b[i] { return false } }
@zzzz 小心,这将在不同的长度上失败。
如果元素类型不支持 ==,这将不起作用。此外,IIUC,Go 没有泛型之类的东西。这意味着您必须为要支持的每种元素类型复制 n' 粘贴此功能。这显然是语言应该附带的东西。事实上,确实如此(尽管具有反射的魔力),维克多提供了答案。事实上,这是在那个答案之上选择的,并且投票率更高,这简直令人抓狂......
Go 作为一门语言倾向于建议不要使用反射,除非绝对必要。是的,每种类型都需要这样做,但无论如何,这通常不是你经常做的事情。此外,reflect.DeepEqual 可能会做一些你意想不到的事情,比如说两个不同的指针是相等的,因为它们指向的值是相等的。
@FiloSottile 长度预先检查,只有当长度不同时才会到达循环。
R
R‌‌‌..

这只是使用@VictorDeryagin 的答案中给出的 reflect.DeepEqual() 的示例。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := []int {4,5,6}
    b := []int {4,5,6}
    c := []int {4,5,6,7}

    fmt.Println(reflect.DeepEqual(a, b))
    fmt.Println(reflect.DeepEqual(a, c))

}

结果:

true
false

Go Playground 中尝试


在性能方面,这与公认的答案相比如何?
K
KeksArmee

如果您有两个 []byte,请使用 bytes.Equal 比较它们。 Golang 文档说:

Equal 返回一个布尔值,报告 a 和 b 是否具有相同的长度并包含相同的字节。一个 nil 参数相当于一个空切片。

用法:

package main

import (
    "fmt"
    "bytes"
)

func main() {
    a := []byte {1,2,3}
    b := []byte {1,2,3}
    c := []byte {1,2,2}

    fmt.Println(bytes.Equal(a, b))
    fmt.Println(bytes.Equal(a, c))
}

这将打印

true
false

为什么这不是顶级
@lurfjurv,因为这只回答了 []byte 的非常具体的情况,而不是任何任意切片
猪不在乎
l
lk_vc

现在,这里是 https://github.com/google/go-cmp

旨在成为 reflect.DeepEqual 的更强大和更安全的替代方案,用于比较两个值在语义上是否相等。

package main

import (
    "fmt"

    "github.com/google/go-cmp/cmp"
)

func main() {
    a := []byte{1, 2, 3}
    b := []byte{1, 2, 3}

    fmt.Println(cmp.Equal(a, b)) // true
}

G
Gabriel Furstenheim

如果您有兴趣编写测试,那么 github.com/stretchr/testify/assert 就是您的朋友。

在文件的最开头导入库:

import (
    "github.com/stretchr/testify/assert"
)

然后在你做的测试里面:


func TestEquality_SomeSlice (t * testing.T) {
    a := []int{1, 2}
    b := []int{2, 1}
    assert.Equal(t, a, b)
}

提示的错误将是:

                Diff:
                --- Expected
                +++ Actual
                @@ -1,4 +1,4 @@
                 ([]int) (len=2) {
                + (int) 1,
                  (int) 2,
                - (int) 2,
                  (int) 1,
Test:           TestEquality_SomeSlice

assert.Equal 在内部使用 reflect.DeepEqual,这可能会使您的测试运行速度变慢,最终导致您的管道运行。
@DeepakSah 你有性能差异的基准吗?根据我的经验,测试中的性能瓶颈不在于断言相等,并且您会获得高质量的消息,从而提高生产力
您可以使用 assert.ElementsMatch(t, a, b) 忽略元素顺序。
b
blackgreen

您不能将 ==!= 与切片一起使用,但如果您可以将它们与元素一起使用,那么 Go 1.18 有一个新函数可以轻松比较两个切片,slices.Equal

Equal 报告两个切片是否相等:长度相同且所有元素相等。如果长度不同,则 Equal 返回 false。否则,元素将按索引递增顺序进行比较,并且比较在第一个不等对处停止。浮点 NaN 不被视为相等。

slices 包导入路径是 golang.org/x/exp/slicesexp 包中的代码是实验性,尚不稳定。最终它将被移动到 Go 1.19 中的标准库 中。

不过,您可以在 Go 1.18 (playground) 中使用它

    sliceA := []int{1, 2}
    sliceB := []int{1, 2}
    equal := slices.Equal(sliceA, sliceB)
    fmt.Println(equal) // true

    type data struct {
        num   float64
        label string
    }

    sliceC := []data{{10.99, "toy"}, {500.49, "phone"}}
    sliceD := []data{{10.99, "toy"}, {200.0, "phone"}}
    equal = slices.Equal(sliceC, sliceD)
    fmt.Println(equal) // true

如果切片的元素不允许 ==!=,您可以使用 slices.EqualFunc 并定义任何对元素类型有意义的比较器函数。


W
Wug

想到一个巧妙的技巧,并认为我会分享。

如果您有兴趣知道两个切片是否相同(即它们为同一数据区域别名)而不是仅仅相等(一个切片的每个索引处的值等于另一个切片的相同索引中的值),那么您可以通过以下方式有效地比较它们:

foo := []int{1,3,5,7,9,11,13,15,17,19}

// these two slices are exactly identical
subslice1 := foo[3:][:4]
subslice2 := foo[:7][3:]

slicesEqual := &subslice1[0]  == &subslice2[0]   && 
               len(subslice1) == len(subslice2)

这种比较有一些注意事项,特别是你不能以这种方式比较空切片,并且切片的容量没有比较,所以这个“相同性”属性只有在从切片读取时才真正有用或重新切分严格较窄的子切片,因为任何增加切片的尝试都会受到切片容量的影响。尽管如此,能够有效地声明“这两个巨大的内存块实际上是同一个块,是或否”是非常有用的。


您的尾括号破坏了代码语法
有人可能想运行 fmt.Printf("%p %p\n", &subslice1[0], &subslice2[0]) 以查看两者共享相同的内存地址。并且只要他将相同的索引与两个切片进行比较,它就可以正常工作。即fmt.Printf("%p %p\n", &subslice1[1], &subslice2[1])
他们会的。 Reslicing 不会重新分配,它会为原始切片使用的存储设置别名,并且切片也始终是连续的,因此无法最终得到一个切片,其中某些索引是正确的,而其他索引则不是。