Jak porównać, czy dwie struktury, wycinki lub mapy są równe?

131

Chcę sprawdzić, czy dwie struktury, wycinki i mapy są równe.

Ale mam problemy z następującym kodem. Zobacz moje komentarze w odpowiednich wierszach.

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
źródło
COnsider również 'nieprawidłowa operacja: t2 == t1 (struktura zawierająca map [string] int nie może być porównana)', dzieje się tak, jeśli struktura nie ma int [] w swojej definicji
Victor

Odpowiedzi:

157

Możesz użyć reflect.DeepEqual lub zaimplementować własną funkcję (która pod względem wydajności byłaby lepsza niż użycie refleksji):

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
źródło
69

reflect.DeepEqual jest często nieprawidłowo używany do porównywania dwóch podobnych struktur, jak w pytaniu.

cmp.Equal jest lepszym narzędziem do porównywania struktur.

Aby zobaczyć, dlaczego refleksja jest nierozsądna, spójrzmy na dokumentację :

Wartości struktur są bardzo równe, jeśli odpowiadające im pola, zarówno wyeksportowane, jak i niewyeksportowane, są bardzo równe.

....

liczby, boole, łańcuchy i kanały - są głęboko równe, jeśli są równe za pomocą operatora == Go.

Jeśli porównamy dwie time.Timewartości z tego samego czasu UTC, t1 == t2będzie fałszywe, jeśli ich strefa czasowa metadanych jest inna.

go-cmpszuka Equal()metody i używa jej do poprawnego porównania czasów.

Przykład:

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
źródło
9
Tak, dokładnie! Podczas pisania testów bardzo ważne jest, aby używać, go-cmpa nie reflect.
Kevin Minehart
Niestety ani odbicie, ani cmp nie działają przy porównywaniu struktury z kawałkiem wskaźników do struktur. Nadal chce, aby wskazówki były takie same.
Violaman
2
@GeneralLeeSpeaking to nieprawda. Z dokumentacji cmp : „Wskaźniki są równe, jeśli podstawowe wartości, na które wskazują, są również równe”
Ilia Choly,
Zgodnie z dokumentacją cmp , używanie cmp jest zalecane tylko podczas pisania testów, ponieważ może wywołać panikę, jeśli obiekty nie są porównywalne.
Marcin
17

Oto jak możesz rzucić swoją własną funkcję 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
źródło
3
Poleciłbym dodać if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }, ponieważ jeden z nich może mieć dodatkowe pola.
OneOfOne
Wszystkie informacje strukturalne są znane w czasie kompilacji. Szkoda, że ​​kompilator nie może w jakiś sposób wykonać tego ciężkiego podnoszenia.
Rick-777,
3
@ Rick-777 po prostu nie ma porównania zdefiniowanego dla plasterków. Tak właśnie chcieli projektanci języka. Nie jest to tak proste do zdefiniowania, jak, powiedzmy, porównanie prostych liczb całkowitych. Czy plasterki są równe, jeśli zawierają te same elementy w tej samej kolejności? Ale co, jeśli ich możliwości się różnią? Itd.
justinas
1
if & a == & b {return true} To nigdy nie zwróci wartości prawda, jeśli parametry do porównania są przekazywane przez wartość.
Sean,
4

Od lipca 2017 możesz korzystać cmp.Equalz cmpopts.IgnoreFieldsopcji.

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
źródło