【问题标题】:How to compare if two structs, slices or maps are equal?如何比较两个结构、切片或映射是否相等?
【发布时间】:2014-08-23 10:02:55
【问题描述】:

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

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

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

【问题讨论】:

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

标签: go go-reflect


【解决方案1】:

您可以使用reflect.DeepEqual,或者您可以实现自己的功能(在性能方面比使用反射更好):

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

m1 := map[string]int{   
    "a":1,
    "b":2,
}
m2 := map[string]int{   
    "a":1,
    "b":2,
}
fmt.Println(reflect.DeepEqual(m1, m2))

【讨论】:

    【解决方案2】:

    这是你如何滚动你自己的函数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
    }
    

    【讨论】:

    • 我建议添加if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false },因为其中一个可能有额外的字段。
    • 所有结构信息在编译时都是已知的。可惜编译器不能以某种方式完成这项繁重的工作。
    • @Rick-777 根本没有为切片定义比较。这就是语言设计者想要的样子。定义起来并不像简单整数的比较那样简单。如果切片以相同的顺序包含相同的元素,它们是否相等?但是,如果他们的能力不同呢?等等。
    • if &a == &b { return true } 如果要比较的参数是按值传递的,这将永远不会评估为 true。
    【解决方案3】:

    reflect.DeepEqual 经常被错误地用于比较两个类似的结构,就像您的问题一样。

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

    要了解为什么不建议使用反射,让我们看看documentation

    如果结构值的对应字段(导出和未导出)深度相等,则结构值深度相等。

    ....

    数字、布尔值、字符串和通道 - 如果使用 Go 的 == 运算符相等,则它们是完全相等的。

    如果我们比较同一 UTC 时间的两个 time.Time 值,如果它们的元数据时区不同,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
    

    重要提示:

    使用cmp.Equal 时要小心,因为它是may lead to a panic condition

    它仅用于测试,因为性能不是目标 如果无法比较这些值,它可能会恐慌。它的倾向 走向恐慌意味着它不适合生产 虚假恐慌可能致命的环境。

    【讨论】:

    • 没错!在编写测试时,使用go-cmp 而不是reflect 非常重要。
    • 根据the cmp documentation,建议仅在编写测试时使用 cmp,因为如果对象不可比较可能会出现恐慌。
    【解决方案4】:

    如果您在单元测试中比较它们,一个方便的替代方法是testify 中的EqualValues 函数。

    【讨论】:

      【解决方案5】:

      如果你打算在测试中使用它,因为July 2017 你可以使用cmp.Equalcmpopts.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
      }
      

      【讨论】:

      • 请注意, cmp 包不适用于生产代码,因为如果它无法比较这些值,它会出现恐慌。它仅用于测试。请参阅此答案中的版主注释和 cmets:stackoverflow.com/a/45222521/3309046
      • 好发现,让我修改答案。
      【解决方案6】:

      如果你想比较简单的一级结构,最好和最简单的方法是if 语句。

      点赞if s1 == s2

      这是一个简单的例子:

      type User struct { 
          name      string 
          email           string
      } 
      
      func main() {
          u1 := User{
                  name: "Iron Man", 
                  email: "ironman@avengers.com",
          }
          u2 := User{
                  name: "Iron Man", 
                  email: "ironman@avengers.com",
          }
              // Comparing 2 structs
              if u1 == u2 {
                  fmt.Println("u1 is equal to u2")
              } else {
                  fmt.Println("u1 is not equal to u2")
              }
      }
      

      结果:u1 is equal to u2

      你可以玩这个here

      【讨论】:

        【解决方案7】:

        比较地图的新方法

        这个提案 (https://github.com/golang/go/issues/47649) 是 Go 泛型未来实现的一部分,它引入了一个新函数来比较两个映射,maps.Equal

        // Equal reports whether two maps contain the same key/value pairs.
        // Values are compared using ==.
        func Equal[M1, M2 constraints.Map[K, V], K, V comparable](m1 M1, m2 M2) bool
        

        示例使用

        strMapX := map[string]int{
            "one": 1,
            "two": 2,
        }
        strMapY := map[string]int{
            "one": 1,
            "two": 2,
        }
        
        equal := maps.Equal(strMapX, strMapY)
        // equal is true
        

        maps 包位于golang.org/x/exp/maps。这是实验性的,不属于 Go 兼容性保证。他们的目标是将其移入 Go 1.19 中的 std lib

        你可以在 gotip 操场上看到它 https://gotipplay.golang.org/p/M0T6bCm1_3m

        【讨论】:

          猜你喜欢
          • 2014-09-20
          • 2013-08-18
          • 2021-05-23
          • 1970-01-01
          • 2015-11-15
          • 2021-12-23
          • 2010-09-13
          • 2021-09-14
          • 2023-03-11
          相关资源
          最近更新 更多