Goland實現Set操做

今日工程接近尾聲,以前的一個set操做使用的redis裏的set,可是這樣很浪費,在內存中聲明set就能夠了,可是Goland中沒有set,因而就在最後手動實現一個git

使用Goland實現Set數據結構操做github

1、背景說明redis

在其餘語言中,set的底層都是利用hash Table實現的,在Goland語言中沒有實現set,但擁有做爲Hash Table實現的字典Map類型,比較二者以後會發現二者之間的一些特徵是很是類似的,咱們或許能夠利用Map來實現Set。數據結構

  • Map中的鍵值是不可重複的,Set中的元素是不可重複的
  • 都只能使用迭代的方式獲取其中的元素
  • Map中的鍵值取出時不保證順序,Set中的元素取出時也不保證任何有序性

咱們能夠發現Set更像是Map的一個簡化版本,也就是value值只有存在或者不存在兩種,到這裏基本上已經清晰了,Map能夠作的全部事情咱們實現的Set均可以完成app

2、基本定義函數

 

type HashSet struct {
	m map[interface{}] bool
}

 

  這個類型聲明中的惟一字段就是map[interface{}] bool,之因此這樣選擇是有緣由的,首先咱們的數據結構選擇的是Map,因此咱們聲明瞭一個value類型是bool的Map,其次,對於不一樣的key類型,咱們使用空接口來實現泛型,Goland語言中沒有泛型,咱們使用空接口來實現,可是空接口的返回值問題我不知道怎麼解決,因此在項目中就直接寫了string類型,缺少可擴展性。最後,value類型選擇爲bool型,由於咱們只須要記錄這個鍵值是否存在,因此選擇佔用空間最小的bool類型值,選用bool類型的兩個預約義常量(false、true)能夠簡潔方便的判斷鍵值是都存在,若使用非零常量判斷,須要較複雜的代碼spa

if v := m["a"]; v != 0 { // 若是「m「中不存在以」"a"做爲鍵的鍵值對
    // 省略若干語句
}
// 對於map[interface{}]bool類型的值來講
if m["a"]{  // 若是「m「中不存在以」"a"做爲鍵的鍵值對
   // 省略若干語句
}  

2、初始化指針

 

func NewHashSet() *HashSet{
	return &HashSet{m: make(map[string]bool)}
}

 

若是直接new(HashSet).m,那麼Map類型的零值是nil,所以要編寫一個專門用於建立和初始化HashSet類型值的函數blog

如上能夠看到的,使用make函數對字段m進行了初始化。而且返回值是*HashSet,而非HashSet,這樣作的好處在下面介紹索引

3、添加元素

 

func (set *HashSet) Add (e string) bool{
	if !set.m[e]{
		set.m[e] = true
		return true
	}
	return false
}

 

Add前面的(set *HashSet)是做爲接受者聲明的,這樣不只在調用時能夠直接使用HashSet.Add(好處1),而且從節約空間的角度出發,當接受者是HashSet時,每次調用都須要對當前的值進行一遍複製,雖然類型聲明中只有一個字段,可是這也是一種開銷,而且不能預估將來這個類型會變得多大,當接受者是*HashSet時,複製的是一個指針,指針是四個字節,基本上都會小於所指類型須要的內存空間(好處2)

4、刪除元素

 

func (set *HashSet) Remove (e string){
	delete(set.m , e)
}

 

5、清除全部元素

 

func (set *HashSet) Clear(){
	set.m = make(map[string]bool)
}

 

這裏的返回值如果HashSet,那麼該方法中的賦值只是對當前的複製品中的字段m進行的複製而已,對original的HashSet沒有進行操做(好處3)

關於垃圾回收,這樣操做以後m綁定了新的字典值,以前綁定的字典值沒有與任何程序實體存在關係,Goland會在以後的某一時刻進行垃圾回收,無需手動處理

6、是否包含某個元素

/方法Contains用於判斷其值是否包含某個元素值。
//這裏判斷結果得益於元素類型爲bool的字段m
func (set *HashSet) Contains(e interface{}) bool {
	return set.m[e]
}

7、獲取元素值的數量

//方法Len用於獲取HashSet元素值數量
func (set *HashSet) Len() int {
    return len(set.m)
}

8、判斷與其餘HashSet類型值是否相同

/方法Same用來判斷兩個HashSet類型值是否相同
func (set *HashSet) Same(other *HashSet) bool {
	if other == nil {
		return false
	}
	if set.Len() != other.Len() {
		return false
	}
	for key := range set.m {
		if !other.Contains(key) {
			return false
		}
	}
	return true
}

兩個 HashSet 類型值相同的必要條件是,它們包含的元素應該是徹底相同的。因爲 HashSet 類型值中的元素的迭代順序老是不肯定的,因此也就不用在乎兩個值在這方面是否一致。若是要判斷兩個 HashSet 類型值是不是同一個值,就須要利用指針運算進行內存地址的比較。

9、獲取全部元素值

func (set *HashSet) Elements() []string{
	initialLen := len(set.m)
	snapshot := make([]string, initialLen)
	actualLen := 0
	for key := range set.m{
		if actualLen < initialLen {
			snapshot[actualLen] = key
		} else {
			snapshot = append(snapshot, key)
		}
		actualLen++
	}
	if actualLen < initialLen{
		snapshot = snapshot[:actualLen]
	}
	return snapshot
}

之因此咱們使用這麼多條語句來實現這個方法是由於須要考慮到在從獲取字段m的值的長度到對m的值迭代完成的這個時間段內,m的值中的元素數量可能發生變化。若是在迭代完成以前,m的值中的元素數量有所增長,使得實際迭代的次數大於先前初始化的快照值的長度 ,那麼咱們再使用appeng函數向快照值追加元素值。這樣作既提升了生成的效率,又不至於在元素數量增長時引起索引越界的運行時恐慌。

對於已被初始化的[]interface{}類型的切片值來講,未被顯示初始化的元素位置上的值均爲nil。若是在迭代完成前,m的值中的元素數量有所減小, 導致快照值的尾部存在若干個沒有任何意義的值爲nil的元素,咱們須要把這些無用的元素從快照中去掉。能夠經過snapshot = snapshot[:actualLen]將無用的元素值從中去掉。

10、個人調用

func ListUnion() ([]string, error){
	Union_Set := NewHashSet()
	Union_Set.Clear()
	res := []string{}
	dbList, err := redisClient.SMembers(DB_SET).Result()
	if err != nil{
		return res, err
	}
	onlyDumpList, err := redisClient.SMembers(List_Only_Dump).Result()
	if err != nil{
		return res, err
	}
	for _, dbName := range dbList{
		Union_Set.Add(dbName)
	}
	for _, dbName := range onlyDumpList{
		Union_Set.Add(dbName)
	}
	res = Union_Set.Elements()
	return res, nil
}

---------------------------------------------------------------------------------

原文做者:https://yangchenglong11.github.io/2016/10/17/Go-%E5%AE%9E%E7%8E%B0set/

相關文章
相關標籤/搜索