Go 編程的三個常見問題
via:
https://medium.com/higher-order-functions/golang-three-common-programming-problems-3ef8baf006af
作者:Saurabh Nayar
四哥水平有限,如有翻譯或理解錯誤,煩請幫忙指出,感謝!
作者在文章中列舉了使用 Go 語言過程碰到的三個常見問題,并且都給出了解決方法,一起來看下作者是怎么解決的!
原文如下:
每種語言都是獨一無二的。這些常見編程問題的解決方案在 Java 中是非常不同的 -- Java 是我以前最喜歡的編程語言。我敢這么說,這些問題的解決方法如果使用 Java 來解決會更加直觀。
Go 語言有解決這些問題的獨特方法。我下面列出的解決方案最初對我來說不是很直觀,但是現(xiàn)在已經(jīng)成為我的下意識的反映。我不確定這些解決方法是否是地道的 Go 語言解決方式,老實說,我也不知道地道的方式是怎么樣的。
也許會有更好的、不同的方式來解決這些問題 -- 我想聽聽你的想法。

現(xiàn)在,我們一起來研究下這些問題。
問題一
問題:我需要維護一個集合,但是 Go 語言里面沒有集合這種數(shù)據(jù)結(jié)構(gòu)。
解決辦法之一:可以用 Go 語言里面的 map 代替集合,map 中的 key 都是唯一的。
package?main
import?"fmt"
type?Set?struct?{
?m?map[string]bool
}
func?NewSet()?Set?{
?m?:=?make(map[string]bool)
?return?Set{m:?m}
}
func?(s?*Set)?Contains(val?string)?bool?{
?_,?ok?:=?s.m[val]
?return?ok
}
func?(s?*Set)?Add(val?string)?{
?s.m[val]?=?true
}
func?(s?*Set)?Remove(val?string)?{
?????delete(s.m,?val)
}
func?main()?{
???s?:=?NewSet()
?s.Add("foo")
?fmt.Printf("s?has?foo:?%t.?s?has?bar:?%t\n",?s.Contains("foo"),?s.Contains("bar"))
??s.Remove("foo")
?fmt.Printf("s?has?foo:?%t.?s?has?bar:?%t\n",?s.Contains("foo"),?s.Contains("bar"))
}
使用 map 作為集合的底層數(shù)據(jù)結(jié)構(gòu)的好處在于,map 基于 hash 表實現(xiàn),減值查找效率高。使用這種方法可以少寫很多代碼。
問題二
問題:我想比較兩個值大小,但是 == 操作符有時會失效。
解決辦法之一:我們需要理解 == 操作符的適用場景。
包含 map 或者 slice 的結(jié)構(gòu)體
type?ABC?struct?{
???a?int
???b?string
???c?[]int
}
Error:
invalid?operation:?a?==?b?(struct?containing?[]int?cannot?be?compared)
包含指針的結(jié)構(gòu)體
從實際意義上講,指針可以進行比較,但事實總是出乎意料。
a,?b?:=?1,?1
fmt.Println(&a?==?&b)?//?False
使用 reflect.DeepEqual
//ABC?-?A?simple?type
type?ABC?struct?{
???a?int
???b?string
???c?[]int
}
var?a?=?ABC{a:?1,?b:?"10",?c:?[]int{1,?2}}
var?b?=?ABC{a:?1,?b:?"10",?c:?[]int{1,?2}}
reflect.DeepEqual(a,?b)
Example?#2
a,?b?:=?1,?1
fmt.Println(&a?==?&b)?//?False
fmt.Println(reflect.DeepEqual(&a,?&b))?//?True
reflect.DeepEqual 可以實現(xiàn)更好的效果。但是如果結(jié)構(gòu)體中 float 或者 時間字段想要忽略,則需要自己編寫比較函數(shù)。
//ABC?-?A?simple?type
type?ABC?struct?{
???a?int
???b?string
???t?time.Time?//?Ignore?time?while?comparing?to?structs
}
var?a?=?ABC{a:?1,?b:?"10",?t:?time.Now()}
var?b?=?ABC{a:?1,?b:?"10",?t:?time.Now()}
fmt.Println(a?==?b,?equals(a,?b))
func?equals(val1,?val2?ABC)?bool?{
????return?val1.a?==?val2.a?&&?val1.b?==?val2.b
}
除非別無選擇,否則一般都不會自己編寫比較函數(shù)。但是與 == 操作符相比,是否要傾向于使用 reflect.DeepEqual。本質(zhì)上,如果 == 比較的結(jié)果為 true,則 reflect.DeepEqual 可以保證比較的結(jié)果為 true,反之為 false。所以你可以默認使用 reflect.DeepEqual,除非程序上有性能上的限制:
func?BenchmarkOperator(t?*testing.B)?{
???for?i?:=?0;?i???????if?a?==?b?{
??????}
???}
}
func?BenchmarkReflectDeep(t?*testing.B)?{
???for?i?:=?0;?i???????if?reflect.DeepEqual(a,?b)?{
??????}
???}
}
BenchmarkOperator-8?????????44614131????????????24.8?ns/op?????????0?B/op??????????0?allocs/op
BenchmarkReflectDeep-8????????823174??????????1558?ns/op??????????96?B/op??????????2?allocs/op
從結(jié)果看出,== 比 reflect.DeepEqual 快多了!
問題三
問題:我需要使用一個 struct 作為 map 的鍵 -- ?但 struct 有想要忽略的 slice、指針或別的字段。
解決辦法之一:Go 語言里面使用 == 操作符比較 map 的鍵,而不要使用 ?reflect.DeepEqual。
解決問題的方法之一就是自定義 key 的創(chuàng)建邏輯。
//Obvious?solution?that?will?not?work
type?A?struct?{
????i?*int
}
i,?j?:=?1,?1
a,?b?:=?A{i:?&i},?A{i:?&j}
m?:=?map[A]bool{}
m[a]?=?true
_,?ok?:=?m[b]
fmt.Println(ok)?//?False?key?b?doesn't?exist?in?map?m
//Custom?keys-?solution
func?customKey(a?A)?int?{
?return?*a.i
}
i,?j?:=?1,?1
a,?b?:=?A{i:?&i},?A{i:?&j}
m?:=?map[int]bool{}
m[customKey(a)]?=?true
_,?ok?:=?m[customKey(b)]
fmt.Println(ok)//?This?will?return?true
有獎問答
問題:從上面問題 2 和問題 3 衍生出一個問題,如何比較兩個 map?
key,?val?:=?"key",?"val"
key1,?val1?:=?"key",?"val"
abc?:=?map[*string]string{&key:?val}
abc2?:=?map[*string]string{&key1:?val1}
def?:=?map[string]*string{key:?&val}
def2?:=?map[string]*string{key1:?&val1}
fmt.Println(reflect.DeepEqual(abc,?abc2))?//false?
fmt.Println(reflect.DeepEqual(def,?def2))?//true
首先需要注意的是,不能使用 == 操作符比較 map,需要使用 ?reflect.DeepEqual。根據(jù) reflect.DeepEqual 的比較規(guī)則,map 的鍵使用 == 操作符比較而值會使用 reflect.DeepEqual ?遞歸比較。
推薦閱讀
站長 polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場和創(chuàng)業(yè)經(jīng)驗
Go語言中文網(wǎng)
每天為你
分享 Go 知識
Go愛好者值得關(guān)注
