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
}
让我们看看我的想法:
声明一种名为 IntSet 的新类型。在其新类型声明下面是 unint64 slice 。
创建一个名为 Has()的方法。它只能接收 IntSet类型,在玩 ints 后,她返回 bool
在她可以玩之前,她需要两个 ints 。她将这些婴儿存放在堆栈上。
迷失语言
此方法的目的是报告集合是否包含非负值x 。这是一个go测试:
func TestExample1(t *testing.T) {
//!+main
var x, y IntSet
fmt.Println(x.Has(9), x.Has(123)) // "true false"
//!-main
// Output:
// true false
}
寻找一些指导,了解此方法在内部的作用。为什么程序员以如此复杂的方式做到了(我觉得我错过了什么)。
退货声明:
return word < len(s.words) && s.words[word]&(1<<bit) != 0
这是操作顺序吗?
return ( word < len(s.words) && ( s.words[word]&(1<<bit)!= 0 )
[words]和&amp; 在以下内容中做什么:
s.words[word]&(1<<bit)!= 0
修改:我开始看到:
s.words[word]&(1<<bit)!= 0
只是一片但不理解&
答案 0 :(得分:2)
当我阅读代码时,我写了一些笔记:
package main
import "fmt"
// A set of bits
type IntSet struct {
// bits are grouped into 64 bit words
words []uint64
}
// x is the index for a bit
func (s *IntSet) Has(x int) bool {
// The word index for the bit
word := x / 64
// The bit index within a word for the bit
bit := uint(x % 64)
if word < 0 || word >= len(s.words) {
// error: word index out of range
return false
}
// the bit set within the word
mask := uint64(1 << bit)
// true if the bit in the word set
return s.words[word]&mask != 0
}
func main() {
nBits := 2*64 + 42
// round up to whole word
nWords := (nBits + (64 - 1)) / 64
bits := IntSet{words: make([]uint64, nWords)}
// bit 127 = 1 * 64 + 63
bits.words[1] = 1 << 63
fmt.Printf("%b\n", bits.words)
for i := 0; i < nWords*64; i++ {
has := bits.Has(i)
if has {
fmt.Println(i, has)
}
}
has := bits.Has(127)
fmt.Println(has)
}
游乐场:https://play.golang.org/p/rxquNZ_23w1
输出:
[0 1000000000000000000000000000000000000000000000000000000000000000 0]
127 true
true
The Go Programming Language Specification
& bitwise AND integers
答案 1 :(得分:1)
peterSO的回答是现场 - 阅读它。但我认为这也可能有助于你理解。
想象一下,我想在1到8的范围内存储一些随机数。在我存储这些数字之后,我会被问到数字n
(也在1-8范围内)是否出现在数字I中早先记录。我们如何存储数字?
一种可能显而易见的方法是将它们存储在切片或地图中。也许我们会选择一个地图,因为查找将是恒定的时间。所以我们创建地图
seen := map[uint8]struct{}{}
我们的代码可能看起来像这样
type IntSet struct {
seen: map[uint8]struct{}
}
func (i *IntSet) AddValue(v uint8) {
i.seen[v] = struct{}{}
}
func (i *IntSet) Has(v uint8) bool {
_, ok := i.seen[v]
return ok
}
对于我们存储的每个数字,我们占用(至少)1字节(8位)的内存。如果我们要存储所有8个数字,我们将使用64位/ 8字节。
但是,顾名思义,这是一个int Set
。我们不关心重复,我们只关心会员资格(Has
为我们提供)。
但是还有另一种方法可以存储这些数字,我们可以在一个字节内完成所有这些操作。由于一个字节提供8位,我们可以使用这8位作为我们看到的值的标记。初始值(二进制表示法)为
00000000 == uint8(0)
如果我们执行AddValue(3)
,我们可以更改第3位并以
00000100 == uint8(3)
^
|______ 3rd bit
如果我们再调用AddValue(8)
,我们就会
10000100 == uint8(132)
^ ^
| |______ 3rd bit
|___________ 8th bit
因此在向IntSet添加3和8之后,我们将内部存储的整数值设置为132.但是我们如何获取132并确定是否设置了特定位?很简单,我们使用按位运算符。
&
运算符是逻辑AND。它将返回运算符每一侧数字之间共同的位值。例如
10001100 01110111 11111111
& 01110100 & 01110000 & 00000001
-------- -------- --------
00000100 01110000 00000001
因此,要了解n
是否在我们的集合中,我们只需执行
our_set_value & (1 << (value_we_are_looking_for - 1))
如果我们搜索4将产生
10000100
& 00001000
----------
0 <-- so 4 is not present
或者如果我们正在搜索8
10000100
& 10000000
----------
10000000 <-- so 8 is present
您可能已经注意到我从value_we_are_looking中减去了1。这是因为我在我们的8位数字中符合1-8。如果我们只想存储七个数字,那么我们可以使用第一个位跳过并假设我们的计数从位#2开始,那么我们就不必减去1,就像你发布的代码一样
假设你了解所有这些,那就是事情变得有趣的地方。到目前为止,我们已经将我们的值存储在uint8中(因此我们只能有8个值,如果省略第一个位则为7)。但是有更大的数字有更多的位,比如uint64。我们可以存储64个值,而不是8个值!但是,如果我们想跟踪的值范围超过1-64,会发生什么?如果我们要存储65怎么办?这是word
s片段来自原始代码的地方。
由于发布的代码会跳过第一位,从现在开始我也会这样做。
我们可以使用第一个uint64来存储数字1 - 63.当我们想要存储数字64-127时,我们需要一个新的uint64。所以我们的切片就像是
[ uint64_of_1-63, uint64_of_64-127, uint64_of_128-192, etc]
现在,要回答关于数字是否在我们的集合中的问题,我们需要首先找到其范围将包含我们的数字的uint64。如果我们要搜索110,我们会想要使用位于索引1(uint64_of_64-128)的uint64,因为110会落在该范围内。
要查找我们需要查看的单词的索引,我们取n / 64
的整数值。在110的情况下,我们将获得1,这正是我们想要的。
现在我们需要检查该数字的具体位。需要检查的位是将110除以64或46时的余数。因此,如果设置了索引1处的第46位,那么我们之前已经看过110。
这就是它在代码中的外观
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) AddValue(x int) {
word := x / 64
bit := x % 64
if word < len(s.words) {
s.words[word] |= (1 << uint64(bit))
}
}
这是一些测试它的代码
func main() {
rangeUpper := 1000
bits := IntSet{words: make([]uint64, (rangeUpper/64)+1)}
bits.AddValue(127)
bits.AddValue(8)
bits.AddValue(63)
bits.AddValue(64)
bits.AddValue(998)
fmt.Printf("%b\n", bits.words)
for i := 0; i < rangeUpper; i++ {
if ok := bits.Has(i); ok {
fmt.Printf("Found %d\n", i)
}
}
}
输出
Found 8
Found 63
Found 64
Found 127
Found 998
注意强>
| =是另一个按位运算符OR。这意味着将两个值组合起来,保持任何值都为1的任何值
10000000 00000001 00000001
& 01000000 & 10000000 & 00000001
-------- -------- --------
11000000 10000001 00000001 <-- important that we
can set the value
multiple times
使用这种方法,我们可以将65535个数据的存储成本从131KB降低到1KB。对于集合成员资格的这种类型的位操作在Bloom Filters
的实现中非常常见答案 2 :(得分:0)
IntSet表示一组整数。可以通过在IntSet中写入单个位来建立任何连续范围的整数的集合中的存在。同样,检查特定整数是否在IntSet中可以通过检查是否设置了与该位对应的特定整数来完成。
因此代码在Intset中找到对应于整数的特定uint64:
word := x/64
然后是uint64中的特定位:
bit := uint(x%64)
然后首先检查正在测试的整数是否在IntSet支持的范围内:
word < len(s.words)
然后是否设置了与特定整数对应的特定位:
&& s.words[word]&(1<<bit) != 0
这部分:
s.words[word]
拉出IntSet的特定uint64,它跟踪有问题的整数是否在集合中。
&
是按位AND。
(1<<bit)
表示取1,将其移位到表示正在测试的特定整数的位位置。
在所讨论的整数之间执行按位AND,如果未设置与整数对应的位,则位移1将返回0,如果设置该位则返回1(意味着,所讨论的整数是IntSet的成员。)