Bagaimana cara membandingkan jika dua struct, slice atau maps sama?

132

Saya ingin memeriksa apakah dua struct, slice, dan maps sama.

Tapi saya mengalami masalah dengan kode berikut. Lihat komentar saya di baris yang relevan.

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.dll
sumber
Pertimbangkan juga 'operasi tidak valid: t2 == t1 (struct yang berisi peta [string] int tidak dapat dibandingkan)', ini terjadi jika struct tidak memiliki int [] dalam definisinya
Victor

Jawaban:

158

Anda dapat menggunakan reflect.DeepEqual , atau Anda dapat menerapkan fungsi Anda sendiri (yang kinerjanya bijaksana akan lebih baik daripada menggunakan refleksi):

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
sumber
69

reflect.DeepEqual sering salah digunakan untuk membandingkan dua struct serupa, seperti dalam pertanyaan Anda.

cmp.Equal adalah alat yang lebih baik untuk membandingkan struct.

Untuk melihat mengapa refleksi tidak disarankan, mari kita lihat dokumentasinya :

Nilai struktur sangat sama jika bidang yang sesuai, baik yang diekspor maupun yang tidak, sangat sama.

....

angka, bools, string, dan saluran - sangat sama jika sama menggunakan operator Go ==.

Jika kita membandingkan dua time.Timenilai waktu UTC yang sama, t1 == t2akan menjadi salah jika zona waktu metadatanya berbeda.

go-cmpmencari Equal()metode dan menggunakannya untuk membandingkan waktu dengan benar.

Contoh:

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
sumber
9
Ya persis! Saat menulis tes, sangat penting untuk digunakan go-cmpdan tidak reflect.
Kevin Minehart
Sayangnya tidak ada reflect atau cmp yang berfungsi untuk membandingkan struct dengan sepotong pointer ke struct. Itu masih ingin petunjuknya sama.
Violaman
2
@GeneralLeeSpeaking itu tidak benar. Dari dokumentasi cmp : "Pointer sama jika nilai dasar yang mereka tunjuk juga sama"
Ilia Choly
Menurut dokumentasi cmp , penggunaan cmp disarankan hanya saat menulis tes, karena dapat membuat panik jika objek tidak sebanding.
martin
17

Inilah cara Anda menggulirkan fungsi Anda sendiri 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
sumber
3
Saya akan merekomendasikan menambahkan if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }, karena salah satu dari mereka dapat memiliki bidang ekstra.
OneOfOne
Semua informasi struktural diketahui pada waktu kompilasi. Sayang sekali kompiler tidak dapat melakukan pengangkatan berat ini dengan cara tertentu.
Rick-777
3
@ Rick-777 tidak ada perbandingan yang ditentukan untuk irisan. Beginilah yang diinginkan oleh desainer bahasa. Tidak sesederhana untuk mendefinisikan seperti, katakanlah, perbandingan bilangan bulat sederhana. Apakah irisan sama jika mengandung elemen yang sama dalam urutan yang sama? Tetapi bagaimana jika kapasitas mereka berbeda? Dll
justinas
1
if & a == & b {return true} Ini tidak akan pernah dievaluasi menjadi benar jika parameter yang akan dibandingkan diteruskan oleh nilai.
Sean
4

Sejak Juli 2017 Anda dapat menggunakan cmp.Equaldengan cmpopts.IgnoreFieldsopsi.

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

Jika Anda membandingkan mereka dalam tes unit , alternatif berguna adalah EqualValues berfungsi dalam bersaksi .

辽 北 狠 人
sumber