本文来源于《The Go Programming Language》

6.5. 示例: Bit数组

Go语言里的集合一般会用map[T]bool这种形式来表示,T代表元素类型。集合用map类型来表示虽然非常灵活,但我们可以以一种更好的形式来表示它。例如在数据流分析领域,集合元素通常是一个非负整数,集合会包含很多元素,并且集合会经常进行并集、交集操作,这种情况下,bit数组会比map表现更加理想。(译注:这里再补充一个例子,比如我们执行一个http下载任务,把文件按照16kb一块划分为很多块,需要有一个全局变量来标识哪些块下载完成了,这种时候也需要用到bit数组)

一个bit数组通常会用一个无符号数或者称之为“字”的slice来表示,每一个元素的每一位都表示集合里的一个值。当集合的第i位被设置时,我们才说这个集合包含元素i。下面的这个程序展示了一个简单的bit数组类型,并且实现了三个函数来对这个bit数组来进行操作:

gopl.io/ch6/intset

// An IntSet is a set of small non-negative integers.// Its zero value represents the empty set.type IntSet struct {    words []uint64}

// Has reports whether the set contains the non-negative value x.func (s *IntSet) Has(x int) bool {    word, bit := x/64, uint(x%64)return word < len(s.words) && s.words[word]&(1<0}// Add adds the non-negative value x to the set.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}// UnionWith sets s to the union of s and t.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)        }    }}

因为每一个字都有64个二进制位,所以为了定位x的bit位,我们用了x/64的商作为字的下标,并且用x%64得到的值作为这个字内的bit的所在位置。UnionWith这个方法里用到了bit位的“或”逻辑操作符号|来一次完成64个元素的或计算。(在练习6.5中我们还会程序用到这个64位字的例子。)

当前这个实现还缺少了很多必要的特性,我们把其中一些作为练习题列在本小节之后。但是有一个方法如果缺失的话我们的bit数组可能会比较难混:将IntSet作为一个字符串来打印。这里我们来实现它,让我们来给上面的例子添加一个String方法,类似2.5节中做的那样:

// String returns the set as a string of the form "{1 2 3}".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()}

这里留意一下String方法,是不是和3.5.4节中的intsToString方法很相似;bytes.Buffer在String方法里经常这么用。当你为一个复杂的类型定义了一个String方法时,fmt包就会特殊对待这种类型的值,这样可以让这些类型在打印的时候看起来更加友好,而不是直接打印其原始的值。fmt会直接调用用户定义的String方法。这种机制依赖于接口和类型断言,在第7章中我们会详细介绍。

现在我们就可以在实战中直接用上面定义好的IntSet了:

var x, y IntSetx.Add(1)x.Add(144)x.Add(9)fmt.Println(x.String()) // "{1 9 144}"

y.Add(9)y.Add(42)fmt.Println(y.String()) // "{9 42}"

x.UnionWith(&y)fmt.Println(x.String()) // "{1 9 42 144}"fmt.Println(x.Has(9), x.Has(123)) // "true false"

这里要注意:我们声明的String和Has两个方法都是以指针类型*IntSet来作为接收器的,但实际上对于这两个类型来说,把接收器声明为指针类型也没什么必要。不过另外两个函数就不是这样了,因为另外两个函数操作的是s.words对象,如果你不把接收器声明为指针对象,那么实际操作的是拷贝对象,而不是原来的那个对象。因此,因为我们的String方法定义在IntSet指针上,所以当我们的变量是IntSet类型而不是IntSet指针时,可能会有下面这样让人意外的情况:

fmt.Println(&x)         // "{1 9 42 144}"fmt.Println(x.String()) // "{1 9 42 144}"fmt.Println(x)          // "{[4398046511618 0 65536]}"

在第一个Println中,我们打印一个*IntSet的指针,这个类型的指针确实有自定义的String方法。第二Println,我们直接调用了x变量的String()方法;这种情况下编译器会隐式地在x前插入&操作符,这样相当远我们还是调用的IntSet指针的String方法。在第三个Println中,因为IntSet类型没有String方法,所以Println方法会直接以原始的方式理解并打印。所以在这种情况下&符号是不能忘的。在我们这种场景下,你把String方法绑定到IntSet对象上,而不是IntSet指针上可能会更合适一些,不过这也需要具体问题具体分析。

- End -

好文点赞收藏

接收对象数组_示例: Bit数组相关推荐

  1. C# 传递数组参数_一维数组_二维数组

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  2. c++ 构造函数数组_从 JS 数组操作到 V8 array.js

    前言 最近在写面试编程题,经常用到数组,经常想偷个懒,用它提供的方法,奈何还是对数组方法使用不熟练,导致写了很多的垃圾代码,很多地方稍加修改的话肯定变得简洁高效优雅? 所以✍这篇文章本着了解一下Jav ...

  3. 无法创建t的通用数组_创建通用数组的问题

    无法创建t的通用数组 在这篇文章中,我们将介绍一篇全面的文章,其中介绍了创建通用数组的问题. Java编程语言于2004年9月在Java 5.0" Tiger"发行版中添加了泛型. ...

  4. 创建数组_如何创建数组

    js数组 js的数组不是典型的数组 典型的数组 元素的数据类型相同 使用连续的内存储存 通过数字下标获取元素 但是js的数组不这样 元素的数据类型可以不同 内存不一定连续的(对象是随机储存的) 不可以 ...

  5. c++ 二维数组_二维数组的声明2019_04_18

    -------------[感谢小郡提供的图片] [广告位招租] ---------------------------------------------------------------- -- ...

  6. @value 数组_深入PHP数组

    1.数组创建 索引数组:数组下标是数字 //自动分配: $cars=array("Volvo","BMW","SAAB"); //手动分配1 ...

  7. python 树状数组_树状数组(Binary Indexed Tree) 总结

    1."树状数组"数据结构的一种应用 对含有n个元素的数组(a[1],...,a[k],...,a[n]): (1)求出第i个到第j个元素的和,sum=a[i]+...+a[j]. ...

  8. 研一寒假02-指针_new分配内存_使用new来创建动态数组_使用动态数组_使用delete来释放new分配的内存...

    #---------------------------------指针-----------------------------------# #include <iostream> i ...

  9. echart data放入数组_线性表(数组、链表、队列、栈)详细总结

    线性表是一种十分基础且重要的数据结构,它主要包括以下内容: 数组 链表 队列 栈 接下来,我将对这四种数据结构做一个详细的总结,其中对链表实现了十几种常见的操作.希望对你有所帮助. 1.数组 数组(A ...

最新文章

  1. android app打开流程_App冷启动,你还要我怎样?
  2. 远程ssh shell 脚本 tcgetattr: Inappropriate ioctl for device错误
  3. Oracle EBS Color 色彩设置
  4. CVE-2012-1876 Internet Exporter堆溢出漏洞分析
  5. 关于MM的几个经典问题及回答
  6. nyoj239月老的难题
  7. Ubuntu16.04LTS安装ROS Kinetic
  8. 判断一个数是不是回文数
  9. 数据结构之图定义及相关概念
  10. linux popen管道,linux进程通信之标准流管道popen
  11. flash 定义主舞台窗口大小
  12. 岁月的脚步,被时间冲刷——如何修复EM ?
  13. typora 有道云笔记_有道云—目前最好用的免费笔记
  14. opengl编程指南
  15. 微信文件没下载过期了
  16. 美国大数据工程师面试指南(建议收藏)
  17. keras-segmentation-master代码详解
  18. bzoj1050 [HAOI2006]旅行comf(并查集)
  19. 阿里云同步gcr.io的镜像
  20. Mybatis中Collection集合标签的使用

热门文章

  1. python编程和c语言编程的区别-通过实例浅析Python对比C语言的编程思想差异
  2. python升级版本命令-CentOS7 下升级Python版本
  3. python有趣代码-你都知道哪些有趣的Python代码?
  4. python手机版ios-使用Python写iOS自动化测试
  5. python for-Python for循环及基础用法详解
  6. python使用input函数时、必须添加提示文字-Python中使用 input 函数来获取输入
  7. 学python用什么书-python有什么好的书籍
  8. 怎么自学python编程-怎么自学python?
  9. python难嘛-我没有基础,能否学会Python?Python难吗?
  10. python基础知识资料-python基础知识整理(值得收藏)