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


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:



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

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
}

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
}

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.