Як порівняти, якщо дві структури, фрагменти чи карти рівні?


131

Я хочу перевірити, чи дві структури, фрагменти та карти рівні.

Але у мене виникають проблеми із наступним кодом. Дивіться мої коментарі у відповідних рядках.

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 також "недійсна операція: t2 == t1 (структура, що містить карту [рядок] int не можна порівняти)", це відбувається, якщо структура не має int [] в рамках свого визначення
Victor

Відповіді:


157

Ви можете використовувати refle.DeepEqual , або ви можете реалізувати власну функцію (яка ефективність була б кращою, ніж використання відображення):

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))

69

reflect.DeepEqual часто неправильно використовується для порівняння двох подібних структур, як у вашому питанні.

cmp.Equal є кращим інструментом для порівняння конструкцій.

Щоб зрозуміти, чому рефлексія недоцільна, розглянемо документацію :

Значення структури глибоко рівні, якщо відповідні їм поля, як експортовані, так і неекспортовані, глибоко рівні.

….

числа, булі, рядки та канали - глибоко рівні, якщо вони рівні за допомогою оператора Go ==.

Якщо ми порівняємо два time.Timeзначення одного і того ж часу UTC, t1 == t2буде помилковим, якщо часовий пояс їх метаданих відрізняється.

go-cmpшукає Equal()метод і використовує його для коректного порівняння часу.

Приклад:

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
Так саме! При написанні тестів дуже важливо використовувати go-cmpі ні reflect.
Кевін Мінехарт

На жаль, ні робота з відображенням, ні cmp не порівнюють структуру з фрагментом покажчиків та структур. Він все ще хоче, щоб покажчики були однаковими.
Віоламан

2
@GeneralLeeSpeaking, що це неправда. З документації cmp : "Покажчики рівні, якщо базові значення, на які вони вказують, також рівні"
Ilia Choly

Відповідно до документації cmp , використовувати cmp рекомендується лише при написанні тестів, оскільки це може панікувати, якщо об'єкти не порівнянні.
мартін

17

Ось як можна виконувати власну функцію 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
Я рекомендую додати if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) { return false }, оскільки в одному з них можуть бути додаткові поля.
OneOfOne

Вся структурна інформація відома під час компіляції. Прикро, що компілятор не може здійснити цей важкий підйом якось.
Rick-777

3
@ Rick-777 просто немає порівняння, визначеного для фрагментів. Ось так хотіли, щоб це були мовні дизайнери. Визначити це не так просто, як, скажімо, порівняння простих цілих чисел. Чи рівні скибочки, якщо вони містять однакові елементи в одному порядку? Але що робити, якщо їх потужність відрізняється? І т. Д.
Justinas

1
if & a == & b {return true} Це ніколи не буде оцінено як істинне, якщо параметри для порівняння передаються за значенням.
Шон

4

З липня 2017 року ви можете використовувати cmp.Equalз cmpopts.IgnoreFieldsопцією.

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
}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.