Как сравнить, равны ли две структуры, срезы или карты?

132

Я хочу проверить, равны ли две структуры, срезы и карты.

Но у меня проблемы со следующим кодом. Смотрите мои комментарии в соответствующих строках.

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
источник
COnsider также «недопустимая операция: t2 == t1 (структура, содержащая map [string] int не может быть сравнена)», это происходит, если структура не имеет int [] в его определении
Виктор

Ответы:

158

Вы можете использовать reflection.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))
OneOfOne
источник
69

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

cmp.Equal - лучший инструмент для сравнения структур.

Чтобы понять, почему рефлексия не рекомендуется, давайте посмотрим на документацию :

Значения структуры полностью равны, если их соответствующие поля, как экспортированные, так и неэкспортированные, полностью равны.

....

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

Если мы сравним два time.Timeзначения одного и того же времени UTC, t1 == t2будет false, если часовой пояс их метаданных отличается.

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 не работают для сравнения структуры с фрагментом указателей на структуры. Он по-прежнему хочет, чтобы указатели были такими же.
Violaman
2
@GeneralLeeSpeaking, это неправда. Из документации cmp : «Указатели равны, если базовые значения, на которые они указывают, также равны»
Илия Чоли
Согласно документации 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 02
Вся структурная информация известна во время компиляции. Жаль, что компилятор не может как-то справиться с этой тяжелой работой.
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
}
WST
источник