Go 語言 示例: 深度相等判斷

2023-03-14 17:00 更新

原文鏈接:https://gopl-zh.github.io/ch13/ch13-03.html


13.3. 示例: 深度相等判斷

來自reflect包的DeepEqual函數(shù)可以對兩個值進行深度相等判斷。DeepEqual函數(shù)使用內(nèi)建的==比較操作符對基礎(chǔ)類型進行相等判斷,對于復(fù)合類型則遞歸該變量的每個基礎(chǔ)類型然后做類似的比較判斷。因為它可以工作在任意的類型上,甚至對于一些不支持==操作運算符的類型也可以工作,因此在一些測試代碼中廣泛地使用該函數(shù)。比如下面的代碼是用DeepEqual函數(shù)比較兩個字符串slice是否相等。

func TestSplit(t *testing.T) {
    got := strings.Split("a:b:c", ":")
    want := []string{"a", "b", "c"};
    if !reflect.DeepEqual(got, want) { /* ... */ }
}

盡管DeepEqual函數(shù)很方便,而且可以支持任意的數(shù)據(jù)類型,但是它也有不足之處。例如,它將一個nil值的map和非nil值但是空的map視作不相等,同樣nil值的slice 和非nil但是空的slice也視作不相等。

var a, b []string = nil, []string{}
fmt.Println(reflect.DeepEqual(a, b)) // "false"

var c, d map[string]int = nil, make(map[string]int)
fmt.Println(reflect.DeepEqual(c, d)) // "false"

我們希望在這里實現(xiàn)一個自己的Equal函數(shù),用于比較類型的值。和DeepEqual函數(shù)類似的地方是它也是基于slice和map的每個元素進行遞歸比較,不同之處是它將nil值的slice(map類似)和非nil值但是空的slice視作相等的值?;A(chǔ)部分的比較可以基于reflect包完成,和12.3章的Display函數(shù)的實現(xiàn)方法類似。同樣,我們也定義了一個內(nèi)部函數(shù)equal,用于內(nèi)部的遞歸比較。讀者目前不用關(guān)心seen參數(shù)的具體含義。對于每一對需要比較的x和y,equal函數(shù)首先檢測它們是否都有效(或都無效),然后檢測它們是否是相同的類型。剩下的部分是一個巨大的switch分支,用于相同基礎(chǔ)類型的元素比較。因為頁面空間的限制,我們省略了一些相似的分支。

gopl.io/ch13/equal

func equal(x, y reflect.Value, seen map[comparison]bool) bool {
    if !x.IsValid() || !y.IsValid() {
        return x.IsValid() == y.IsValid()
    }
    if x.Type() != y.Type() {
        return false
    }

    // ...cycle check omitted (shown later)...

    switch x.Kind() {
    case reflect.Bool:
        return x.Bool() == y.Bool()
    case reflect.String:
        return x.String() == y.String()

    // ...numeric cases omitted for brevity...

    case reflect.Chan, reflect.UnsafePointer, reflect.Func:
        return x.Pointer() == y.Pointer()
    case reflect.Ptr, reflect.Interface:
        return equal(x.Elem(), y.Elem(), seen)
    case reflect.Array, reflect.Slice:
        if x.Len() != y.Len() {
            return false
        }
        for i := 0; i < x.Len(); i++ {
            if !equal(x.Index(i), y.Index(i), seen) {
                return false
            }
        }
        return true

    // ...struct and map cases omitted for brevity...
    }
    panic("unreachable")
}

和前面的建議一樣,我們并不公開reflect包相關(guān)的接口,所以導(dǎo)出的函數(shù)需要在內(nèi)部自己將變量轉(zhuǎn)為reflect.Value類型。

// Equal reports whether x and y are deeply equal.
func Equal(x, y interface{}) bool {
    seen := make(map[comparison]bool)
    return equal(reflect.ValueOf(x), reflect.ValueOf(y), seen)
}

type comparison struct {
    x, y unsafe.Pointer
    t reflect.Type
}

為了確保算法對于有環(huán)的數(shù)據(jù)結(jié)構(gòu)也能正常退出,我們必須記錄每次已經(jīng)比較的變量,從而避免進入第二次的比較。Equal函數(shù)分配了一組用于比較的結(jié)構(gòu)體,包含每對比較對象的地址(unsafe.Pointer形式保存)和類型。我們要記錄類型的原因是,有些不同的變量可能對應(yīng)相同的地址。例如,如果x和y都是數(shù)組類型,那么x和x[0]將對應(yīng)相同的地址,y和y[0]也是對應(yīng)相同的地址,這可以用于區(qū)分x與y之間的比較或x[0]與y[0]之間的比較是否進行過了。

// cycle check
if x.CanAddr() && y.CanAddr() {
    xptr := unsafe.Pointer(x.UnsafeAddr())
    yptr := unsafe.Pointer(y.UnsafeAddr())
    if xptr == yptr {
        return true // identical references
    }
    c := comparison{xptr, yptr, x.Type()}
    if seen[c] {
        return true // already seen
    }
    seen[c] = true
}

這是Equal函數(shù)用法的例子:

fmt.Println(Equal([]int{1, 2, 3}, []int{1, 2, 3}))        // "true"
fmt.Println(Equal([]string{"foo"}, []string{"bar"}))      // "false"
fmt.Println(Equal([]string(nil), []string{}))             // "true"
fmt.Println(Equal(map[string]int(nil), map[string]int{})) // "true"

Equal函數(shù)甚至可以處理類似12.3章中導(dǎo)致Display陷入死循環(huán)的帶有環(huán)的數(shù)據(jù)。

// Circular linked lists a -> b -> a and c -> c.
type link struct {
    value string
    tail *link
}
a, b, c := &link{value: "a"}, &link{value: "b"}, &link{value: "c"}
a.tail, b.tail, c.tail = b, a, c
fmt.Println(Equal(a, a)) // "true"
fmt.Println(Equal(b, b)) // "true"
fmt.Println(Equal(c, c)) // "true"
fmt.Println(Equal(a, b)) // "false"
fmt.Println(Equal(a, c)) // "false"

練習(xí) 13.1: 定義一個深比較函數(shù),對于十億以內(nèi)的數(shù)字比較,忽略類型差異。

練習(xí) 13.2: 編寫一個函數(shù),報告其參數(shù)是否為循環(huán)數(shù)據(jù)結(jié)構(gòu)。



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號