如何在Go中实现BitSet?

15

在 Go 中找不到 BitSet 包,所以我尝试自己实现它。 我希望使用一个 uint64 数组来存储位。

我需要知道要分配的 uint64 数组中的位数。 在 Java 中,我可以定义一个接受整数参数的构造函数。 虽然 Go 没有提供构造函数,但当用户调用 new() 时,我该如何正确地初始化 BitSet '对象' 呢?

5个回答

16

Go的标准big.Int可用于作为位集合:

package main

import (
    "fmt"
    "math/big"
)

func main() {
    var bits big.Int
    for i := 1000; i < 2000; i++ {
        bits.SetBit(&bits, i, 1)
    }
    for i := 0; i < 10000; i++ {
        if bits.Bit(i) != 0 {
            fmt.Println(i)
        }
    }
}

https://play.golang.org/p/xbIK-boouqC


5

将bitSet声明为一个私有结构体:

type bitSet struct {
  len int
  array []uint64
}

暴露接口 BitSet:

type BitSet interface {
  Has(pos int) bool
  Add(pos int) bool
  Len() int
}

同时还要公开一个名为NewBitSet的函数:

func NewBitSet(len int) BitSet {
  return &bitSet{len, make(uint64, (len+7) / 8) }
}

这是一种Go语言中的封装方式: 共享接口而不是实现。

6
我不完全同意这种解决方案。我认为位集太特定以至于不能成为接口。它允许你拥有实现相同接口的不同位集,但你真的需要这样的东西吗?在我看来,你可以设计一个位集实现,能够很好地适用于几乎所有的用途,然后始终使用它。多个实现很可能只会使人们感到困惑。 - Evan Shaw
我觉得在Go语言中,没有统一的对象初始化方式。 :( new(BitSet), BitSet.New(), NewBitSet(),它们之间有什么关系? - Stephen Hsu
1
我认为除了像Ivan那样不导出类型之外,没有其他方法可以禁用new(BitSet)。另外,由于Go不幸地没有重载函数或可选函数参数,您将不得不为不同的参数组合创建具有不同名称的函数。我建议尽可能少地创建此类函数。 - Evan Shaw
是的,在合适的情况下,这肯定是一种好的技术。我只是不认为这是这种情况之一,对此我将简单地同意不同意。 - Evan Shaw
"BitSet" 在不同的地方有完全不同的含义。例如,Java 的 BitSet 没有长度属性。 - newacct
显示剩余4条评论

3
如果您使用[]uint64切片来存储数据,那么空切片可以作为空的BitSet。实际上,向nil切片添加元素会为您分配一个新数组,虽然语言规范似乎并没有保证这一点。有了这种设置,new(BitSet)将立即可用。例如:
bitset.go:
package bitset

const size = 64

type bits uint64

// BitSet is a set of bits that can be set, cleared and queried.
type BitSet []bits

// Set ensures that the given bit is set in the BitSet.
func (s *BitSet) Set(i uint) {
    if len(*s) < int(i/size+1) {
        r := make([]bits, i/size+1)
        copy(r, *s)
        *s = r
    }
    (*s)[i/size] |= 1 << (i % size)
}

// Clear ensures that the given bit is cleared (not set) in the BitSet.
func (s *BitSet) Clear(i uint) {
    if len(*s) >= int(i/size+1) {
        (*s)[i/size] &^= 1 << (i % size)
    }
}

// IsSet returns true if the given bit is set, false if it is cleared.
func (s *BitSet) IsSet(i uint) bool {
    return (*s)[i/size]&(1<<(i%size)) != 0
}

bitset_test.go:

package bitset

import "fmt"

func ExampleBitSet() {
    s := new(BitSet)
    s.Set(13)
    s.Set(45)
    s.Clear(13)
    fmt.Printf("s.IsSet(13) = %t; s.IsSet(45) = %t; s.IsSet(30) = %t\n",
               s.IsSet(13), s.IsSet(45), s.IsSet(30))
    // Output: s.IsSet(13) = false; s.IsSet(45) = true; s.IsSet(30) = false
}

1
语言规范保证append适用于空切片。它在一个关于append的例子中使用了一个空切片。(为了使这个结论有效,我必须假设Go语言规范中的示例是规范性的,而不是C标准中的。) - Roland Illig

1

简短的回答是,当客户端调用new()时,你无法正确地初始化BitSet对象。

最好的方法是使你的BitSet的零值有效。这就是像list.Listsync.Mutexbig.Int这样的类型所做的。这样你就知道客户端不可能得到一个无效的值。

下一件最好的事情是创建一个类似构造函数的函数(在这种情况下命名为NewBitSet),并期望客户端调用它。


那我可以调用List.New()而不是new(List)吗? 对于一个新的包,我该如何知道如何创建一个新对象? - Stephen Hsu
我认为这是正确的,尽管我可能记错了。即使允许调用new(List),你也不会想这么做,因为你无法正确地初始化对象。你可以通过阅读文档来找出如何创建新对象,就像在包中执行其他任何操作一样。通常,这些类似构造函数的函数名称中都会有单词“New”。 - Evan Shaw
原来你可以通过new()创建一个带有未导出字段的对象。除非该对象具有某种初始化函数可供调用,否则它似乎并不是很有用。 - Evan Shaw
实际上,如果您阅读列表包的文档,您会发现“List 的零值是一个空列表,可以立即使用。”这意味着评估new(List)并立即开始使用生成的列表对象是完全有效的。 - Matt
@Matt 你说得对,我已经相应地更改了我的答案。我喜欢想象当我最初回答这个问题时,列表可能没有以这种方式工作。 ;) - Evan Shaw

0

我也实现了自己的, 它是这样工作的:

bm := bitmask.New(4)        // [4]{0000}
bm.Set(3)                   // [4]{0001}
bm.Slice(2, 4).ToggleAll()  // [4]{0010}
bm.Slice(1, 3).ToggleAll()  // [4]{0100}
bm.Slice(0, 2).ToggleAll()  // [4]{1000}
bm.Clear()                  // [4]{0000}

正如其他人所回答的那样,有一个惯例使用New函数来模拟构造函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接