英文:
Why does go-cmp Equal() say that the structs are not deeply equal, even though all fields are deeply equal?
问题
我一直在使用reflect.DeepEqual
来深度比较具有循环指针的结构体。由于这在处理映射时不起作用,并且为了获得更好的测试输出,我已经切换到了go-cmp
。
现在我注意到,尽管cmp.Equal
被认为是reflect.DeepEqual
的替代品,但在某些情况下,即使前者正确工作,结果也会有所不同,尽管它们实际上是深度相等的。
有人可以告诉我为什么在这种情况下结果不同,并且最好的情况下,如何修复它?
Go Playground中的代码:https://play.golang.com/p/rLWKwMlAfwu
(更新为使用fmt.Printf()
,因为我无法在Playground中运行testing
)
diff的输出:
StrongConnect() mismatch (-want +got):
&⟪ref#0⟫main.Edge{
StartNode: &⟪ref#1⟫main.Node{
Variable: 1,
- Low: &⟪ref#0: 0xc00005c120⟫(...),
+ Low: &⟪ref#0: 0xc00005c120⟫(...),
High: &{StartNode: &⟪ref#1⟫(...), EndNode: &{Variable: 2}, EdgeType: 1, Weight: 1},
},
EndNode: &{Variable: 2},
EdgeType: 0,
Weight: 1,
}
英文:
I've been using reflect.DeepEqual
to deeply compare structs using circular pointers. As that does not work with maps and for better test output, I've switched to go-cmp
.
Now I had to take note that even though cmp.Equal
is supposed to be a drop-in replacement for reflect.DeepEqual
, in cases where the former works correctly, there's a different result in this case, even though it actually is deeply equal.
Can anyone tell me why the result is different in this case and ideally, how to fix it?
Code in Go playground: https://play.golang.com/p/rLWKwMlAfwu
(Updated to use fmt.Printf(), because I couldn't get testing
running in playground)
Output for diff:
StrongConnect() mismatch (-want +got):
&⟪ref#0⟫main.Edge{
StartNode: &⟪ref#1⟫main.Node{
Variable: 1,
- Low: &⟪ref#0: 0xc00005c120⟫(...),
+ Low: &⟪ref#0: 0xc00005c120⟫(...),
High: &{StartNode: &⟪ref#1⟫(...), EndNode: &{Variable: 2}, EdgeType: 1, Weight: 1},
},
EndNode: &{Variable: 2},
EdgeType: 0,
Weight: 1,
}
答案1
得分: 2
reflect.DeepEqual
在比较具有循环结构的结构体时比cmp.Equal
更宽松(并且可能是错误的)。
当且仅当图中的节点和边的集合相同时,cmp.Equal
才认为重叠的图是等价的。注意:在此图比较中,Node
和Edge
结构体都是节点。
在你的示例中,两个图/结构体是重叠的,但是wantEdge0
有一个额外的Edge
结构体作为根节点(在前面插入)。
以下是你的数据结构中循环的简化表示:
wantEdge0 := &main.Edge{ // n0
StartNode: &main.Node{ // n1
Low: &main.Edge{} // n2
},
}
wantEdge0.StartNode.Low.StartNode = wantEdge0.StartNode // n1
got := wantEdge0.StartNode.Low // n2
因此,存在两个不同的循环:
wantEdge0 [n0] -> wantEdge0.StartNode [n1] -> got [n2] -> wantEdge0.StartNode [n1]
got [n2] -> wantEdge0.StartNode [n1] -> got [n2]
以下是一个简单的示例,演示了reflect.DeepEqual
和cmp.Equal
之间的差异:
package main
import (
"fmt"
"reflect"
"github.com/google/go-cmp/cmp"
)
type Node struct {
Next *Node
Value int
}
func main() {
a0 := &Node{}
a1 := &Node{}
a2 := &Node{}
a0.Next = a1
a1.Next = a2
a2.Next = a1
b1 := &Node{}
b2 := &Node{}
b1.Next = b2
b2.Next = b1
fmt.Println("DeepEqual\tcmp.Equal")
fmt.Printf("\t%v\t%v\t\t独立的图\n", reflect.DeepEqual(a1, b1), cmp.Equal(a1, b1))
fmt.Printf("\t%v\t%v\t\t相同的图,不同的根节点\n", reflect.DeepEqual(a1, a2), cmp.Equal(a1, a2))
fmt.Printf("\t%v\t%v\t\t相同的图,有/无前置节点\n", reflect.DeepEqual(a0, a1), cmp.Equal(a0, a1))
}
输出结果:
$ ./compare
DeepEqual cmp.Equal
true true 独立的图
true true 相同的图,不同的根节点
true false 相同的图,有/无前置节点
解决方案:我建议为你的“want”结构体和测试输入分配完全独立的结构体。这样,“want”和“got”结构体就不会重叠,它们应该按预期进行比较。
参考资料:
- https://github.com/google/go-cmp/commit/5a6f75716e1203a923a78c9efb94089d857df0f6
- https://github.com/google/go-cmp/issues/74
英文:
reflect.DeepEqual
is more lax than cmp.Equal
when comparing structures with cycles (and arguably incorrect).
cmp.Equal
will only consider overlapping graphs equivalent if the set of nodes and edges in the graph is the same. Note: Both Node
and Edge
structs are nodes in this graph comparison.
In your example, the 2 graphs/structs overlap but wantEdge0
is has an extra Edge
struct as the root node (prepended).
Here is cut down representation of the cycles in your data structures:
wantEdge0 := &main.Edge{ // n0
StartNode: &main.Node{ // n1
Low: &main.Edge{} // n2
},
}
wantEdge0.StartNode.Low.StartNode = wantEdge0.StartNode // n1
got := wantEdge0.StartNode.Low // n2
Hence there are the 2 different cycles:
wantEdge0 [n0] -> wantEdge0.StartNode [n1] -> got [n2] -> wantEdge0.StartNode [n1]
got [n2] -> wantEdge0.StartNode [n1] -> got [n2]
Here is a simple example that demonstrates this difference between reflect.DeepEqual
and cmp.Equal
:
package main
import (
"fmt"
"reflect"
"github.com/google/go-cmp/cmp"
)
type Node struct {
Next *Node
Value int
}
func main() {
a0 := &Node{}
a1 := &Node{}
a2 := &Node{}
a0.Next = a1
a1.Next = a2
a2.Next = a1
b1 := &Node{}
b2 := &Node{}
b1.Next = b2
b2.Next = b1
fmt.Println("DeepEqual\tcmp.Equal")
fmt.Printf("\t%v\t%v\t\tIndependent graphs\n", reflect.DeepEqual(a1, b1), cmp.Equal(a1, b1))
fmt.Printf("\t%v\t%v\t\tSame graph, different root\n", reflect.DeepEqual(a1, a2), cmp.Equal(a1, a2))
fmt.Printf("\t%v\t%v\t\tSame graph prepend vs no prepend\n", reflect.DeepEqual(a0, a1), cmp.Equal(a0, a1))
}
Output:
$ ./compare
DeepEqual cmp.Equal
true true Independent graphs
true true Same graph, different root
true false Same graph prepend vs no prepend
Solution: I would recommend allocating completely separate structures for your want struct and the test input. This way the want and got structs won't overlap and they should compare as intended.
References:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论