Wie kann man vergleichen, ob zwei Strukturen, Slices oder Maps gleich sind?

131

Ich möchte überprüfen, ob zwei Strukturen, Slices und Maps gleich sind.

Aber ich habe Probleme mit dem folgenden Code. Siehe meine Kommentare in den entsprechenden Zeilen.

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
quelle
COnsider auch 'ungültige Operation: t2 == t1 (Struktur mit map [string] int kann nicht verglichen werden)', dies passiert, wenn die Struktur kein int [] in seiner Definition hat
Victor

Antworten:

69

reflect.DeepEqual wird oft fälschlicherweise verwendet, um zwei ähnliche Strukturen zu vergleichen, wie in Ihrer Frage.

cmp.Equal ist ein besseres Werkzeug zum Vergleichen von Strukturen.

Um zu sehen, warum Reflexion schlecht beraten ist, schauen wir uns die Dokumentation an :

Strukturwerte sind zutiefst gleich, wenn die entsprechenden Felder, sowohl exportiert als auch nicht exportiert, sehr gleich sind.

....

Zahlen, Bools, Strings und Kanäle - sind zutiefst gleich, wenn sie mit dem Operator == von Go gleich sind.

Wenn wir zwei time.TimeWerte derselben UTC-Zeit vergleichen, t1 == t2ist dies falsch, wenn ihre Metadaten-Zeitzone unterschiedlich ist.

go-cmpsucht nach der Equal()Methode und verwendet diese, um die Zeiten korrekt zu vergleichen.

Beispiel:

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
quelle
9
Ja genau! Beim Schreiben von Tests ist es sehr wichtig, diese zu verwenden go-cmpund nicht reflect.
Kevin Minehart
Leider funktionieren weder Reflect noch CMP für den Vergleich einer Struktur mit einem Teil von Zeigern auf Strukturen. Es möchte immer noch, dass die Zeiger gleich sind.
Violaman
2
@ GeneralLeeSpeaking das stimmt nicht. Aus der cmp-Dokumentation : "Zeiger sind gleich, wenn die zugrunde liegenden Werte, auf die sie zeigen, ebenfalls gleich sind"
Ilia Choly
Laut der CMP-Dokumentation wird die Verwendung von CMP nur beim Schreiben von Tests empfohlen, da es zu Panik kommen kann, wenn Objekte nicht vergleichbar sind.
Martin
17

So rollen Sie Ihre eigene Funktion: 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
quelle
3
Ich würde das Hinzufügen empfehlen if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }, da eines von ihnen zusätzliche Felder haben könnte.
OneOfOne
Alle Strukturinformationen sind zur Kompilierungszeit bekannt. Es ist eine Schande, dass der Compiler dieses schwere Heben in keiner Weise ausführen kann.
Rick-777
3
@ Rick-777 Für Slices ist einfach kein Vergleich definiert. So wollten es die Sprachdesigner. Es ist nicht so einfach zu definieren wie beispielsweise der Vergleich einfacher Ganzzahlen. Sind Slices gleich, wenn sie dieselben Elemente in derselben Reihenfolge enthalten? Was aber, wenn sich ihre Kapazitäten unterscheiden? Etc.
Justinas
1
if & a == & b {return true} Dies wird niemals als true ausgewertet, wenn die zu vergleichenden Parameter als Wert übergeben werden.
Sean
4

Seit Juli 2017 können Sie cmp.Equalmit cmpopts.IgnoreFieldsOption verwenden.

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

Wenn Sie sie sind zu vergleichen in Unit - Test , eine praktische Alternative ist EqualValues funktionieren in auszusagen .

辽 北 狠 人
quelle