2つの構造体、スライス、またはマップが等しいかどうかを比較するにはどうすればよいですか?


131

2つの構造体、スライス、マップが等しいかどうかを確認したいと思います。

しかし、次のコードで問題が発生しています。関連する行で私のコメントを参照してください。

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


また、「無効な操作:t2 == t1(map [string] intを含む構造体は比較できません)」、構造体が定義内にint []を持たない場合に発生します
Victor

回答:


157

Reflect.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 あなたの質問のように、2つの構造体を比較するために誤って使用されることがよくあります。

cmp.Equal 構造体を比較するための優れたツールです。

リフレクションが不適切である理由を確認するために、ドキュメントを見てみましょう:

エクスポートされる値とエクスポートされない値の両方に対応するフィールドが深く等しい場合、構造体の値は深く等しくなります。

....

数値、ブール値、文字列、チャネル-Goの==演算子を使用して等しい場合、それらは完全に等しくなります。

time.Time同じUTC時間の2つの値を比較するとt1 == t2、メタデータのタイムゾーンが異なる場合はfalseになります。

go-cmpEqual()メソッドを探し、それを使用して時間を正しく比較します。

例:

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です。
ケビンミネハート2017

残念ながら、reflectもcmpも、構造体を構造体へのポインターのスライスと比較するために機能しません。それでもポインタが同じであることを望んでいます。
-Violaman、

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 }そのうちの1つに余分なフィールドがある可能性があるため、を追加することをお勧めします。
OneOfOne 2014

構造情報はすべてコンパイル時にわかります。コンパイラがこのような重い作業を何らかの方法で実行できないのは残念です。
Rick-777 14

3
@ Rick-777スライスに定義されている比較はありません。これは、言語デザイナーが望んでいた方法です。単純な整数の比較など、定義するのは簡単ではありません。同じ順序で同じ要素が含まれている場合、スライスは等しいですか?しかし、それらの容量が異なる場合はどうなりますか?その他
justinas 14

1
if&a ==&b {return true}比較するパラメータが値で渡されている場合、これは決してtrueと評価されません。
ショーン

4

以来2017年7月、あなたは使うことができcmp.Equalcmpopts.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
}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.