这种方法发生了什么?

时间:2018-02-08 01:31:03

标签: go methods

 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
 }

让我们看看我的想法:

  1. 声明一种名为 IntSet 的新类型。在其新类型声明下面是 unint64 slice

  2. 创建一个名为 Has()的方法。它只能接收 IntSet类型,在玩 ints 后,她返回 bool

  3. 在她可以玩之前,她需要两个 ints 。她将这些婴儿存放在堆栈上。

  4. 迷失语言

  5. 此方法的目的是报告集合是否包含非负值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 
    

    只是一片但不理解&

3 个答案:

答案 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

Arithmetic operators

&    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

Playground of above

注意
| =是另一个按位运算符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的成员。)