Como comparar se duas estruturas, fatias ou mapas são iguais?

131

Quero verificar se duas estruturas, fatias e mapas são iguais.

Mas estou tendo problemas com o seguinte código. Veja meus comentários nas linhas 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
fonte
Considere também 'operação inválida: t2 == t1 (struct contendo mapa [cadeia] int não pode ser comparado)', isto acontece se a estrutura não tem int [] dentro de sua definição
Victor

Respostas:

157

Você pode usar reflect.DeepEqual ou pode implementar sua própria função (que seria melhor em termos de desempenho do que usar reflexão):

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))
OneOfOne
fonte
69

reflect.DeepEqual geralmente é usado incorretamente para comparar duas estruturas semelhantes, como na sua pergunta.

cmp.Equal é uma ferramenta melhor para comparar estruturas.

Para ver por que a reflexão é desaconselhada, vejamos a documentação :

Os valores de estrutura são profundamente iguais se seus campos correspondentes, exportados e não exportados, forem profundamente iguais.

....

números, bools, strings e canais - são profundamente iguais se forem iguais usando o operador Go's ==.

Se compararmos dois time.Timevalores do mesmo horário UTC, t1 == t2será falso se o fuso horário dos metadados for diferente.

go-cmpprocura o Equal()método e o usa para comparar corretamente os tempos.

Exemplo:

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
fonte
9
Sim, exatamente! Ao escrever testes, é muito importante usar go-cmpe não reflect.
Kevin Minehart 19/09/17
Infelizmente, nem o trabalho nem o cmp refletem a comparação de uma estrutura com uma fatia de ponteiros e estruturas. Ele ainda quer que os ponteiros sejam os mesmos.
21419 Violaman
2
@GeneralLeeSpeaking isso não é verdade. Da documentação do cmp : "Os ponteiros são iguais se os valores subjacentes para os quais eles apontam também forem iguais"
Ilia Choly
De acordo com a documentação do cmp , o uso do cmp é recomendado apenas ao escrever testes, pois pode entrar em pânico se os objetos não forem comparáveis.
martin
17

Veja como você lançaria sua própria função 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
fonte
3
Eu recomendo adicionar if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }, porque um deles pode ter campos extras.
OneOfOne
Toda a informação estrutural é conhecida em tempo de compilação. É uma pena que o compilador não possa fazer esse trabalho pesado de alguma forma.
21714 Rick-777
3
@ Rick-777 simplesmente não há comparação definida para fatias. Era assim que os designers de linguagem queriam que fosse. Não é tão simples definir como, digamos, a comparação de números inteiros simples. As fatias são iguais se contiverem os mesmos elementos na mesma ordem? Mas e se suas capacidades diferirem? Etc.
justinas
1
if & a == & b {return true} Isso nunca será avaliado como true se os parâmetros a serem comparados estiverem sendo transmitidos por valor.
21418 Sean
4

Desde julho de 2017, você pode usar cmp.Equalcom a cmpopts.IgnoreFieldsopção

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
fonte
3

Se você os estiver comparando no teste de unidade , uma alternativa útil é a função EqualValues no testify .

辽 北 狠
fonte