检查两个切片的相等性

Checking the equality of two slices

提问人:wei2912 提问时间:3/9/2013 最后编辑:blackgreenwei2912 更新时间:8/23/2023 访问量:222028

问:

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

package main

import "fmt"

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

这不会编译为:

无效操作:S1 == S2(切片只能与 nil 进行比较)

Go 比较 切片

评论

0赞 guettli 4/29/2023
pkg.go.dev/golang.org/x/exp/slices#Equal

答:

220赞 Stephen Weinberg 3/9/2013 #1

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

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
}

评论

21赞 zzzz 3/9/2013
建议:。for i, v := range a { if v != b[i] { return false } }
41赞 Filippo Valsorda 3/31/2014
@zzzz 小心,这将在不同的长度上失败。
2赞 allyourcode 2/12/2015
如果元素类型不支持 ==,则此操作不起作用。此外,IIUC、Go 没有类似泛型的东西。这意味着您必须为要支持的每个元素类型复制并粘贴此函数。这显然是语言应该附带的东西。事实上,它确实如此(尽管有反射的魔力),维克多提供了答案。事实上,这是在答案之上选择的,而且投票率更高,这简直令人抓狂......
8赞 Stephen Weinberg 3/25/2015
Go 作为一种语言,除非绝对必要,否则倾向于建议不要使用反射。是的,每种类型都需要这样做,但无论如何,这通常不是您经常做的事情。另外,反思。DeepEqual 可能会做一些你意想不到的事情,例如说两个不同的指针相等,因为它们指向的值是相等的。
3赞 icza 8/4/2015
@FiloSottile 事先检查长度,则仅当长度不同时才能达到循环。
340赞 Victor Deryagin 3/9/2013 #2

您应该使用 reflect。DeepEqual() ()

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

DeepEqual 报告 x 和 y 是否“深度相等”,定义为 遵循。如果 以下情况适用。不同类型的价值观从来都不是深刻的 平等。

当数组值的相应元素为 高度平等。

如果结构值的对应字段,则结构值深度相等,两者 出口和未出口,是高度平等的。

如果两者均为零,则函数值基本相等;否则他们不是 高度平等。

如果界面值具有深度相等的具体值,则它们非常相等 值。

如果映射值是相同的映射对象,或者如果它们 具有相同的长度及其对应的键(使用 Go 匹配 平等)映射到深度平等的价值观。

如果指针值相等,则使用 Go 的 == 运算符,或者如果它们指向深度相等的值。

当满足以下所有条件时,切片值基本相等:它们 既为 nil 或两者都为 non,它们的长度相同,并且 它们指向同一基础数组的相同初始条目 (即 &x[0] == &y[0]) 或其对应的元素(最多 长度)非常相等。请注意,非 nil 空切片和 nil slice(例如,[]byte{} 和 []byte(nil))并不相等。

其他值 - 数字、布尔值、字符串和通道 - 很深 如果使用 Go 的 == 运算符相等,则相等。

评论

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

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

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 中试用

评论

2赞 Manu Manjunath 8/10/2021
就性能而言,这与公认的答案相比如何?
58赞 KeksArmee 1/6/2018 #4

如果有两个,请使用字节比较它们。相等。Golang 文档说:[]byte

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
9赞 Gabriel Furstenheim 4/19/2019 #5

如果你有兴趣写一个测试,那么 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

评论

0赞 Deepak Sah 11/21/2019
assert.Equal内部使用,这可能会使测试运行速度变慢,并最终使管道运行速度变慢。reflect.DeepEqual
2赞 Gabriel Furstenheim 11/22/2019
@DeepakSah 你们有性能差异的基准吗?根据我的经验,测试中的性能瓶颈并不在于断言相等,并且您会获得高质量的消息,从而提高生产力
5赞 marcelosalloum 9/30/2020
您可以使用忽略元素顺序。assert.ElementsMatch(t, a, b)
17赞 lk_vc 7/31/2019 #6

就目前而言,这是 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
}
3赞 Wug 8/13/2021 #7

想到了一个巧妙的技巧,我想我会分享。

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

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)

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

评论

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

你不能将 或 与切片一起使用,但如果你可以将它们与元素一起使用,那么 Go 1.18 有一个新功能,可以轻松比较两个切片、切片。相等==!=

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

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

不过,您可以在 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

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

评论

2赞 Eric 9/3/2022
在 go1.19 中,它仍然在"golang.org/x/exp/slices"
0赞 pvlbzn 2/24/2023 #9

要获得一套完整的答案:这是一个泛型的解决方案。

func IsEqual[A comparable](a, b []A) bool {
    // Can't be equal if length differs
    if len(a) != len(b) {
        return false
    }

    // Empty arrays trivially equal
    if len(a) == 0 {
        return true
    }

    // Two pointers going towards each other at every iteration
    left := 0
    right := len(a) - 1

    for left <= right {
        if a[left] != b[left] || a[right] != b[right] {
            return false
        }

        left++
        right--
    }

    return true
}

代码使用“两个指针”的策略,这带来了运行时的复杂度,然而,这仍然比逐个线性检查少两倍。n / 2O(n)

更新:根据@doublethink13修复了相等检查错误

评论

0赞 doublethink13 6/10/2023
您的代码中存在一个微妙的错误。如果要比较长度为 1 的切片,则此操作将失败;如果要比较只有中间元素不同的奇数长度切片,则此操作将失败。 需要更改为for left < rightfor left <= right
1赞 pvlbzn 6/12/2023
你是绝对正确的@doublethink13,编辑!
0赞 mapcuk 4/25/2023 #10

有函数断言。ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) 用于检查切片。

感谢 Aaron 的评论,这并不明显,所以我添加突出显示此断言需要 lib “github.com/stretchr/testify/assert”

评论

1赞 Aaron 6/6/2023
您应该添加它是第三方模块:testify。
-1赞 Aditya Choudhary 7/13/2023 #11

Go 语言为此提供了内置的支持实现。Golang 中的函数用于检查 x 和 y 是否“深度相等”。要访问此功能,需要在程序中导入 reflect 包。reflect.DeepEqual()

语法:func DeepEqual(x, y interface{}) bool

参数:此函数采用两个参数,其值为任意类型,即 x、y。

返回值:此函数返回布尔值。

例如: 如果要检查map_1和map_2是否相等

result := reflect.DeepEqual(map_1, map_2)

如果 map_1 和 map_2 相等,则 result 将为 true,如果 map_1 和 map_2 不相等,则 result 将为 false

1赞 PRATHEESH PC 7/13/2023 #12

Golang 引入了一个包 Slices,其中包含各种功能,可用于任何类型的 Slices。我们可以使用 Equal 函数来报告两个切片是否相等。

https://cs.opensource.google/go/x/exp/+/06a737ee:slices/slices.go;l=22

// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in increasing index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[E comparable](s1, s2 []E) bool {
    if len(s1) != len(s2) {
        return false
    }
    for i := range s1 {
        if s1[i] != s2[i] {
            return false
        }
    }
    return true
}

法典

package main

import (
    "fmt"

    "golang.org/x/exp/slices"
)

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

    equal := slices.Equal(s1, s2)
    fmt.Println("Is Equal ? ", equal)
}

1赞 Z. Kosanovic 8/23/2023 #13

从 Go 1.21 开始,您可以使用标准库中的泛型函数:slices.Equal()

package main

import (
    "fmt"
    "slices"
)

func main() {
    s1 := []int{1, 4, 1, 4, 2, 1, 3, 5, 6, 2}
    s2 := []int{1, 4, 1, 4, 2, 1, 3, 5, 6, 2}

    fmt.Println(slices.Equal(s1, s2))

    s3 := []string{"foo", "bar"}
    s4 := []string{"foo", "baz"}

    fmt.Println(slices.Equal(s3, s4))
}

游乐场: https://go.dev/play/p/_WwU0BSwN2P