检查两个切片的相等性

wei*_*912 243 go go-reflect

如何检查两个切片是否相等?

Vic*_*gin 241

你应该使用reflect.DeepEqual()

DeepEqual是Go的==运算符的递归松弛.

DeepEqual报告x和y是否"非常相等",定义如下.如果以下情况之一适用,则相同类型的两个值非常相等.不同类型的值永远不会相等.

当相应的元素非常相等时,数组值非常相等.

如果导出和未导出的相应字段非常相等,则结构值非常相等.

如果两者都是零,则Func值非常相等; 否则他们就不是很平等.

如果它们具有非常相同的具体值,则接口值非常相等.

如果它们是相同的地图对象,或者如果它们具有相同的长度并且它们相应的键(使用Go相等匹配)映射到非常相等的值,则它们是非常相等的.

如果使用Go的==运算符将指针值相等,或者它们指向的值非常相等,则指针值非常相等.

当满足以下所有条件时,切片值非常相等:它们都是零或两者都是非零,它们具有相同的长度,并且它们指向相同底层数组的相同初始条目(即&x [0 ] ==&y [0])或其相应的元素(直到长度)非常相等.注意,非零空切片和零切片(例如,[] byte {}和[] byte(nil))不是非常相等.

其他值 - 数字,bool,字符串和通道 - 如果使用Go的==运算符相等则非常相等.

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

Ste*_*erg 144

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

func testEq(a, b []Type) bool {

    // If one is nil, the other must also be nil.
    if (a == nil) != (b == nil) { 
        return false; 
    }

    if len(a) != len(b) {
        return false
    }

    for i := range a {
        if a[i] != b[i] {
            return false
        }
    }

    return true
}
Run Code Online (Sandbox Code Playgroud)

  • @zzzz小心,这将失败不同的长度. (15认同)
  • 建议:`for i,v:= range a {if v!= b [i] {return false}}`. (14认同)
  • 除非绝对必要,否则作为一种语言倾向于建议不使用反射.是的,它需要为每种类型完成,但通常不是你经常做的事情.另外,reflect.DeepEqual可能会做一些你不期望的事情,例如说两个不同的指针是相等的,因为它们指向的值是相等的. (5认同)
  • 如果元素类型不支持==,则不起作用.此外,IIUC,Go没有像仿制药那样的东西.这意味着您必须为要支持的每种元素类型复制n'粘贴此函数.这显然应该附带语言.事实上,它确实(虽然有反思的魔力),而维克多提供了答案.事实上,这个选择高于答案,而且投票率更高,这简直令人抓狂...... (2认同)
  • @FiloSottile预先检查长度,只有长度不同才能达到循环. (2认同)
  • @VladDidenko现在我明白了你的意思.你是对的.虽然在大多数情况下我会把一个`nil`切片等于一个长度为0的切片,因为我的切片可以简单地取决于我如何创建/初始化它.例如,使用短变量声明,编写`s:= [] string {}`通常不会是`nil`,但是使用`var`它将是`var s [] string`,它会将它初始化为`nil `.请注意,两者都打印为空切片`[]`:[Go Playground](http://play.golang.org/p/MJYiSq1q39) (2认同)
  • @Stefen-Weinberg 这个解决方案是否假设数组已排序或我遗漏了什么? (2认同)

Aka*_*all 40

这只是使用@ 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))

}
Run Code Online (Sandbox Code Playgroud)

结果:

true
false
Run Code Online (Sandbox Code Playgroud)

  • 就性能而言,这与公认的答案相比如何? (2认同)

Kek*_*mee 20

如果你有两个[]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))
}
Run Code Online (Sandbox Code Playgroud)

这将打印

true
false
Run Code Online (Sandbox Code Playgroud)


bla*_*een 20

Go 1.21及以上版本

该函数现在位于标准库中:slices.Equal

从 1.18 到 1.20

你不能将==or!=与切片一起使用,但如果你可以将它们与元素一起使用,那么 Go 1.18 包中有一个新函数golang.org/x/exp可以轻松比较两个切片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
Run Code Online (Sandbox Code Playgroud)

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


Gab*_*eim 9

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

在文件的最开头导入库:

import (
    "github.com/stretchr/testify/assert"
)
Run Code Online (Sandbox Code Playgroud)

然后在测试中执行以下操作:


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

提示的错误将是:

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

  • 您可以使用“assert.ElementsMatch(t, a, b)”来忽略元素顺序。 (5认同)
  • @DeepakSah 你有性能差异的基准吗?根据我的经验,测试中的性能瓶颈并不等于断言,并且您会获得高质量的消息,从而提高生产力 (2认同)

lk_*_*_vc 8

而现在,这里是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
}
Run Code Online (Sandbox Code Playgroud)