如何比较两个结构,切片或映射是否相等?


131

我想检查两个结构,切片和映射是否相等。

但是我遇到了以下代码的问题。在相关行中查看我的评论。

package main

import (
    "fmt"
    "reflect"
)

type T struct {
    X int
    Y string
    Z []int
    M map[string]int
}

func main() {
    t1 := T{
        X: 1,
        Y: "lei",
        Z: []int{1, 2, 3},
        M: map[string]int{
            "a": 1,
            "b": 2,
        },
    }

    t2 := T{
        X: 1,
        Y: "lei",
        Z: []int{1, 2, 3},
        M: map[string]int{
            "a": 1,
            "b": 2,
        },
    }

    fmt.Println(t2 == t1)
    //error - invalid operation: t2 == t1 (struct containing []int cannot be compared)

    fmt.Println(reflect.ValueOf(t2) == reflect.ValueOf(t1))
    //false
    fmt.Println(reflect.TypeOf(t2) == reflect.TypeOf(t1))
    //true

    //Update: slice or map
    a1 := []int{1, 2, 3, 4}
    a2 := []int{1, 2, 3, 4}

    fmt.Println(a1 == a2)
    //invalid operation: a1 == a2 (slice can only be compared to nil)

    m1 := map[string]int{
        "a": 1,
        "b": 2,
    }
    m2 := map[string]int{
        "a": 1,
        "b": 2,
    }
    fmt.Println(m1 == m2)
    // m1 == m2 (map can only be compared to nil)
}

http://play.golang.org/p/AZIzW2WunI


Consider还“无效的操作:t2 == t1(无法比较包含map [string] int的结构)”,如果该结构在其定义内没有int [],则会发生这种情况
Victor

Answers:



69

reflect.DeepEqual 通常被错误地用来比较两个类似的结构,如您的问题。

cmp.Equal 是比较结构的更好工具。

要了解为什么不建议使用反射,让我们看一下文档

如果结构的值对应的字段(导出的和未导出的)都非常相等,则该值非常相等。

....

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

如果我们比较time.Time相同UTC时间的两个值,t1 == t2则当它们的元数据时区不同时将为false。

go-cmp寻找Equal()方法并使用它来正确比较时间。

例:

m1 := map[string]int{
    "a": 1,
    "b": 2,
}
m2 := map[string]int{
    "a": 1,
    "b": 2,
}
fmt.Println(cmp.Equal(m1, m2)) // will result in true

9
对,就是这样!在编写测试时,使用go-cmp而不是非常重要reflect
凯文·米涅哈特

不幸的是,反射和cmp都无法将结构与指向结构的指针进行比较。它仍然希望指针是相同的。
维奥拉曼

2
@GeneralLeeSpeaking是错误的。从cmp文档中:“如果指针所指向的基础值也相等,则指针是相等的”
Ilia Choly

根据cmp文档,仅在编写测试时才建议使用cmp,因为如果对象不具有可比性,可能会感到恐慌。
马丁

17

这是您滚动自己的函数的方法http://play.golang.org/p/Qgw7XuLNhb

func compare(a, b T) bool {
  if &a == &b {
    return true
  }
  if a.X != b.X || a.Y != b.Y {
    return false
  }
  if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) {
    return false
  }
  for i, v := range a.Z {
    if b.Z[i] != v {
      return false
    }
  }
  for k, v := range a.M {
    if b.M[k] != v {
      return false
    }
  }
  return true
}

3
我建议添加if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false },因为其中之一可能会有额外的字段。
OneOfOne 2014年

所有结构信息在编译时都是已知的。遗憾的是编译器无法以某种方式完成繁重的工作。
Rick-777

3
@ Rick-777根本没有为切片定义比较。这就是语言设计师想要的样子。定义并不像比较简单整数那样简单。如果切片包含相同顺序的相同元素,切片是否相等?但是,如果他们的能力不同呢?等等
justinas 2014年

1
如果&a ==&b {return true}如果要比较的参数是按值传递的,则永远不会为true。
肖恩

4

20177月起,您可以使用cmp.Equalwith cmpopts.IgnoreFields选项。

func TestPerson(t *testing.T) {
    type person struct {
        ID   int
        Name string
    }

    p1 := person{ID: 1, Name: "john doe"}
    p2 := person{ID: 2, Name: "john doe"}
    println(cmp.Equal(p1, p2))
    println(cmp.Equal(p1, p2, cmpopts.IgnoreFields(person{}, "ID")))

    // Prints:
    // false
    // true
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.