Comment comparer si deux structures, tranches ou cartes sont égales?

132

Je veux vérifier si deux structures, tranches et cartes sont égales.

Mais je rencontre des problèmes avec le code suivant. Voir mes commentaires aux lignes pertinentes.

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
la source
COnsider aussi 'opération invalide: t2 == t1 (la structure contenant la carte [chaîne] int ne peut pas être comparée)', cela se produit si la structure n'a pas d'int [] dans sa définition
Victor

Réponses:

69

reflect.DeepEqual est souvent mal utilisé pour comparer deux structures similaires, comme dans votre question.

cmp.Equal est un meilleur outil pour comparer les structures.

Pour voir pourquoi la réflexion n'est pas judicieuse, regardons la documentation :

Les valeurs Struct sont profondément égales si leurs champs correspondants, à la fois exportés et non exportés, sont profondément égaux.

....

nombres, booléens, chaînes et canaux - sont profondément égaux s'ils sont égaux à l'aide de l'opérateur == de Go.

Si nous comparons deux time.Timevaleurs de la même heure UTC, t1 == t2sera faux si leur fuseau horaire de métadonnées est différent.

go-cmprecherche la Equal()méthode et l'utilise pour comparer correctement les temps.

Exemple:

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
la source
9
Oui, exactement! Lors de l'écriture de tests, il est très important d'utiliser go-cmpet non reflect.
Kevin Minehart
Malheureusement, ni reflect ni cmp ne fonctionnent pour comparer une structure avec une tranche de pointeurs vers des structures. Il veut toujours que les pointeurs soient les mêmes.
Violaman
2
@GeneralLeeSpeaking ce n'est pas vrai. De la documentation cmp : "Les pointeurs sont égaux si les valeurs sous-jacentes vers lesquelles ils pointent sont également égales"
Ilia Choly
Selon la documentation de cmp , l'utilisation de cmp n'est conseillée que lors de l'écriture de tests, car cela peut paniquer si les objets ne sont pas comparables.
martin
17

Voici comment lancer votre propre fonction 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
la source
3
Je recommanderais d'ajouter if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }, car l'un d'eux pourrait avoir des champs supplémentaires.
OneOfOne
Toutes les informations structurelles sont connues au moment de la compilation. C'est dommage que le compilateur ne puisse pas faire ce gros travail d'une manière ou d'une autre.
Rick-777
3
@ Rick-777, aucune comparaison n'est définie pour les tranches. C'est ainsi que les concepteurs de langage ont voulu que ce soit. Ce n'est pas aussi simple à définir que, disons, la comparaison d'entiers simples. Les tranches sont-elles égales si elles contiennent les mêmes éléments dans le même ordre? Mais que faire si leurs capacités diffèrent? Etc.
justinas
1
if & a == & b {return true} Cela ne sera jamais évalué à true si les paramètres à comparer sont passés par valeur.
Sean
4

Depuis juillet 2017, vous pouvez utiliser cmp.Equalavec cmpopts.IgnoreFieldsoption.

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
la source