首页 > 技术文章 > Golang理解-集合

vinsent 2019-09-20 10:37 原文

集合


Go语言里的集合一般会用map[T]bool这种形式来表示,T代表元素类型。
集合用map类型来表示虽然非常灵活,但我们可以以一种更好的形式来表示它。例如:在数据流分析领域,集合元素通常是一个非负整数,集合会包含很多元素,并且集合会经常进行并集、交集操作,这种情况下,bit数组会比map表现更加理想

我们知道在go语言中,出了几本数据类型外; 还有slice,map,struct,interface这些复杂数据类型;
但是我们发现在go中好像没有集合(set)这种数据类型,那么我们该如何通过go的基础类型和复杂数据类型来实现集合呢?
下面看一个int类型集合的实现

type IntSet struct {
    words []uint64
}

func (s *IntSet) Has(x int) bool {
    word, bit := x/64, uint(x%64)
    return word < len(s.words) && s.words[word]&(1<<bit) != 0
}

func (s *IntSet) Add(x int) {
    word, bit := x/64 ,uint(x%64)
    for word >= len(s.words) {
        s.words = append(s.words, 0)
    }
    s.words[word] |= 1 << bit
}

func (s *IntSet) UnionWith(t *IntSet) {
    for i, tword := range t.words {
        if i < len(s.words) {
            s.words[i] |= tword
        } else {
            s.words = append(s.words, tword)
        }
    }
}

func (s *IntSet) String() string {
    var buf bytes.Buffer
    buf.WriteByte('{')
    for i, word := range s.words {
        if word == 0 {
            continue
        }
        for j := 0; j < 64; j++ {
            if word&(1 << uint(j)) != 0 {
                if buf.Len() > len("{") {
                    buf.WriteByte(' ')
                }
                fmt.Fprintf(&buf, "%d", 64 * i + j)
            }
        }
    }
    buf.WriteByte('}')
    return buf.String()
}

方法


上面的代码定义了一个IntSet的结构体,结构体中的字段是一个[]int64的切片,用于存储int64类型的整型数据;
func (s *IntSet) Has(x int) bool 为IntSet结构体提供了一个方法,用于判断集合内是否有某个元素了;
func (s *IntSet) Add(x int) 为IntSet结构体提供了添加元素的方法;
func (s *IntSet) UnionWith(t *IntSet) 为IntSet结构体提供了一个方法,用于将2个IntSet类型的集合做并集;

设计思路


一个bit数组通常会用一个无符号数或者称之为“字”的slice来表示,每一个元素的每一位都表示集合里的一个值。当集合的第i位被设置时,我们才说这个集合包含元素i
因为每一个字都有64个二进制位,所以为了定位x的bit位,我们用了x/64的商作为字的下标,并且用x%64得到的值作为这个字内的bit的所在位置。
UnionWith这个方法里用到了bit位的“或”逻辑操作符号|来一次完成64个元素的或计算

推荐阅读