¿Cómo comparar si dos estructuras, cortes o mapas son iguales?

131

Quiero verificar si dos estructuras, cortes y mapas son iguales.

Pero me encuentro con problemas con el siguiente código. Vea mis comentarios en las líneas relevantes.

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

leiyonglin
fuente
COnsider también 'operación no válida: t2 == t1 (la estructura que contiene map [string] int no se puede comparar)', esto sucede si la estructura no tiene int [] dentro de su definición
Victor

Respuestas:

157

Puede usar reflect.DeepEqual , o puede implementar su propia función (cuyo rendimiento sería mejor que usar la reflexión):

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))
Uno de uno
fuente
69

reflect.DeepEqual a menudo se usa incorrectamente para comparar dos estructuras similares, como en su pregunta.

cmp.Equal es una mejor herramienta para comparar estructuras.

Para ver por qué la reflexión no es aconsejable, veamos la documentación :

Los valores de estructura son profundamente iguales si sus campos correspondientes, tanto exportados como no exportados, son profundamente iguales.

....

números, bools, cadenas y canales: son profundamente iguales si son iguales utilizando el operador Go's ==.

Si comparamos dos time.Timevalores de la misma hora UTC, t1 == t2será falso si su zona horaria de metadatos es diferente.

go-cmpbusca el Equal()método y lo usa para comparar correctamente los tiempos.

Ejemplo:

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
Cole Bittel
fuente
9
¡Sí exactamente! Al escribir pruebas, es muy importante usar go-cmpy no reflect.
Kevin Minehart
Desafortunadamente, ni reflect ni cmp funcionan para comparar una estructura con una porción de punteros a estructuras. Todavía quiere que los punteros sean los mismos.
Violaman
2
@GeneralLeeSpeaking eso no es cierto. De la documentación de cmp : "Los punteros son iguales si los valores subyacentes a los que apuntan también son iguales"
Ilia Choly
Según la documentación de cmp , se recomienda usar cmp solo al escribir pruebas, ya que puede entrar en pánico si los objetos no son comparables.
Martin
17

Así es como desarrollarías tu propia función 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
}
Ilia Choly
fuente
3
Recomiendo agregar if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }, porque uno de ellos podría tener campos adicionales.
OneOfOne
Toda la información estructural se conoce en tiempo de compilación. Es una pena que el compilador no pueda hacer este trabajo pesado de alguna manera.
Rick-777
3
@ Rick-777 simplemente no hay comparación definida para las rebanadas. Así es como los diseñadores de idiomas querían que fuera. No es tan simple de definir como, por ejemplo, la comparación de enteros simples. ¿Son iguales las rebanadas si contienen los mismos elementos en el mismo orden? Pero, ¿y si sus capacidades difieren? Etc.
justinas
1
if & a == & b {return true} Esto nunca se evaluará como verdadero si los parámetros para comparar se pasan por valor.
Sean
4

Desde julio de 2017 puede usar cmp.Equalcon cmpopts.IgnoreFieldsopción.

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
}
wst
fuente
3

Si los está comparando en la prueba unitaria , una alternativa útil es la función EqualValues en testify .

辽 北 狠 人
fuente