ChatGPT解决这个技术问题 Extra ChatGPT

How to compare if two structs, slices or maps are equal?

I want to check if two structs, slices and maps are equal.

But I'm running into problems with the following code. See my comments at the relevant lines.

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 also 'invalid operation: t2 == t1 (struct containing map[string]int cannot be compared)', this happens if the struct has no int[] within his definition

O
OneOfOne

You can use reflect.DeepEqual, or you can implement your own function (which performance wise would be better than using reflection):

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))

Is it possible to ignore some field like the id, such as with cmp.Equal(p1, p2, cmpopts.IgnoreFields(person{}, "ID"))?
M
Machavity

reflect.DeepEqual is often incorrectly used to compare two like structs, as in your question.

cmp.Equal is a better tool for comparing structs.

To see why reflection is ill-advised, let's look at the documentation:

Struct values are deeply equal if their corresponding fields, both exported and unexported, are deeply equal. .... numbers, bools, strings, and channels - are deeply equal if they are equal using Go's == operator.

If we compare two time.Time values of the same UTC time, t1 == t2 will be false if their metadata timezone is different.

go-cmp looks for the Equal() method and uses that to correctly compare times.

Example:

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

Important Note:

Be careful when using cmp.Equal as it may lead to a panic condition

It is intended to only be used in tests, as performance is not a goal and it may panic if it cannot compare the values. Its propensity towards panicking means that its unsuitable for production environments where a spurious panic may be fatal.


Yes exactly! When writing tests, it's very important to use go-cmp and not reflect.
According to the cmp documentation, using cmp is advised only when writing tests, because it may panic if objects aren't comparable.
I
Ilia Choly

Here's how you'd roll your own function 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
}

Update: Go 1.18

import (
    "golang.org/x/exp/maps"
    "golang.org/x/exp/slices"
)

func compare(a, b *T) bool {
    if a == b {
        return true
    }
    if a.X != b.X {
        return false
    }
    if a.Y != b.Y {
        return false
    }
    if !slices.Equal(a.Z, b.Z) {
        return false
    }
    return maps.Equal(a.M, b.M)
}

I'd recommend adding if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }, because one of them could have extra fields.
All the structural information is known at compile time. It's a shame the compiler can't do this heavy lifting in some way.
@Rick-777 there's simply no comparison defined for slices. This is how the language designers wanted it to be. It's not as simple to define as, say, comparison of simple integers. Are slices equal if they contain same elements in the same order? But what if their capacities differ? Etc.
if &a == &b { return true } This will never evaluate to true if the parameters to compare are being passed by value.
It looks like you changed the first statement to if a == b ... which doesn't compile (hence the question from OP). I suppose you wanted to follow up on Sean's note. You'd have to change the T into *T then that first if() would compile (and work as expected).
w
wst

If you intend to use it in tests, since July 2017 you can use cmp.Equal with cmpopts.IgnoreFields option.

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
}

Note that the cmp package is not meant for production code, as it panics if it cannot compare the values. It is only intended for tests. See the moderator annotation and comments in this answer: stackoverflow.com/a/45222521/3309046.
Good find, let me amend the answer.
r
rustyx

If you're comparing them in unit test, a handy alternative is EqualValues function in testify.


R
Riyaz Khan

If you want to compare simple one-level structs, the best and simple method is the if statement.

Like this if s1 == s2

Here is a simple example:

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")
        }
}

Result: u1 is equal to u2

You can play with this here.


b
blackgreen

New way to compare maps

This proposal (https://github.com/golang/go/issues/47649) that is part of the future implementation of Go generics introduces a new function to compare two maps, 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

Example use

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 package is found in golang.org/x/exp/maps. This is experimental and outside of Go compatibility guarantee. They aim to move it into the std lib in Go 1.19

You can see it working in gotip playground https://gotipplay.golang.org/p/M0T6bCm1_3m


关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now