检查两个切片的相等性
Posted
技术标签:
【中文标题】检查两个切片的相等性【英文标题】:Checking the equality of two slices 【发布时间】:2013-02-25 01:24:10 【问题描述】:如何检查两个切片是否相等?
【问题讨论】:
这个问题确实是关于一个简单的任务,但 IMO 这是一个真正的问题,有一个非常具体的答案。据我所知,我不记得曾经活跃于围棋标记问题的人如何将它作为“不是一个真正的问题”来关闭,这超出了我的理解。具体来说:问题没有歧义,完整,仅限于单个(虽然简单)的问题,非修辞性的,并且可以以当前形式准确准确地回答。==
运算符在 Go 中仅针对某些类型定义,因此,这个问题也是一个合法的问题。
不过,这不是关闭原因中提到的任何事情(“不能以目前的形式合理地回答”)。
哈哈哈,我不敢相信这会因为“不是一个真正的问题”而关闭。 1) 不难说出被问到的内容。 2)问题不是模棱两可/不完整/广泛/不合理。这是相当的滥用!
关闭的原因是没有证据表明 OP 为研究或解决此问题所做的任何努力。
看起来目前太容易将 Downvote 按钮(“我认为这个问题没有表现出努力,而且问得不好”)与关闭按钮(“我认为这不可能由于以下原因而回答...”)。可能是因为关闭投票是免费的。
【参考方案1】:
您需要遍历切片中的每个元素并进行测试。未定义切片的相等性。但是,如果您要比较 []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 长度是预先检查的,只有当长度不同时才会到达循环。【参考方案2】:
你应该使用reflect.DeepEqual()
DeepEqual 是 Go 的 == 运算符的递归松弛。
DeepEqual 报告 x 和 y 是否“深度相等”,定义为 跟随。两个相同类型的值是深度相等的,如果其中一个 以下情况适用。不同类型的值永远不会很深 相等。
当它们的对应元素是 非常平等。
如果结构值对应的字段,则两者都非常相等 出口和未出口,是完全平等的。
如果两个函数都为零,则函数值是完全相等的;否则他们不是 非常平等。
如果接口值具有高度相等的具体值,则它们是高度相等的 价值观。
如果地图值是相同的地图对象,或者如果它们是相同的地图对象,则地图值是高度相等的 具有相同的长度及其对应的键(使用 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 的切片与已初始化的切片不匹配已声明但未初始化。【参考方案3】:这只是使用@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试试吧
【讨论】:
在性能方面,这与接受的答案相比如何?【参考方案4】:如果您有两个[]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 的非常具体的情况,而不是任何任意切片 猪别在意【参考方案5】:如果您有兴趣编写测试,那么github.com/stretchr/testify/assert
是您的朋友。
在文件的最开头导入库:
import (
"github.com/stretchr/testify/assert"
)
然后在你做的测试里面:
func TestEquality_SomeSlice (t * testing.T)
a := []int1, 2
b := []int2, 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)
忽略元素顺序。【参考方案6】:
现在,这里是https://github.com/google/go-cmp
旨在成为
reflect.DeepEqual
的更强大、更安全的替代方案,用于比较两个值在语义上是否相等。
package main
import (
"fmt"
"github.com/google/go-cmp/cmp"
)
func main()
a := []byte1, 2, 3
b := []byte1, 2, 3
fmt.Println(cmp.Equal(a, b)) // true
【讨论】:
【参考方案7】:想到了一个巧妙的技巧,并认为我会分享。
如果您有兴趣了解两个切片是否相同(即它们对同一数据区域进行别名)而不是仅仅相等(每个索引处的值一个切片的值等于另一个切片的相同索引中的值),那么您可以通过以下方式有效地比较它们:
foo := []int1,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])
等
他们会的。重新切片不会重新分配,它会为原始切片使用的存储设置别名,并且切片也始终是连续的,因此无法最终得到一个切片,其中某些索引是正确的,而其他索引则不是。以上是关于检查两个切片的相等性的主要内容,如果未能解决你的问题,请参考以下文章