Go语言圣经 - 第4章 复合数据类型 - 4.3 Map

第四章 复合数据类型

基础数据类型是Go语言世界的原子

复合数据类型包括四种:slice、map、 struct、array

数组和结构体是聚合类型,它们的值由许多元素或成员构成,数组和结构体都是固定内存大小的数据结构,,相比之下,slice和map则是动态的数据结构,它们将根据动态增长

4.3 Map

哈希表是一种非常巧妙的数据结构,它是一个无序key/value对的集合,通过给定的key,可以在常熟时间复杂度内检索、更新或者删除value

一个map就是一个哈希表的引用,map类型可以写成map[K]V,所有的k和所有的v分别代表相同的类型,但是必须是支持 == 比较运算符的数据类型

使用内置函数make创建map

先声明再初始化

ages := make(map[string]int)
ages["alice"] =34
ages["charlie"] = 31

使用字面值语法创建map

声明时直接初始化

ages0 := map[string]int{
   "alice":34,
   "charlie":31,
}

使用内置函数delete删除元素

fmt.Println(ages)//map[alice:34 charlie:31]
delete(ages,"alice")
fmt.Println(ages)//map[charlie:31]

map还支持++、+=等简短赋值语法

这个其实跟value值相关的

ages["bob"] =ages["bob"]+1
ages["jane"]++
ages["john"]+=1
fmt.Println(ages)//map[bob:1 charlie:31 jane:1 john:1]

但是注意:map中的元素不是变量,无法进行取地址操作

map 的遍历

可以使用for循环遍历而出,但是迭代顺序是不确定的。好处是每次随机遍历可以强制要求程序不会依赖具体的哈希函数的实现

for name,age := range ages{
   fmt.Println(name,age)
}
charlie 31
jane 1
john 1
bob 1

map的顺序遍历

如果想要顺序遍历map,必须显式的对key排序,可以使用sort包的String函数进行排序

var names = []string
for name := range ages {//类似得,slice中的索引和值,map中的key和value,遍历时如果只定定义一个变
//量,默认访问的是第一个元素(索引orkey),如果想只遍历第二个元素,那就要使用_进行丢弃
   names = append(names,name)
}
sort.Strings(names)
for _,name := range names {
   fmt.Printf("%s\t%d\n",name,ages[name])
}

流程其实就是创建一个空的切片,然后遍历获取map中的key并放入切片,对切片元素进行排序,最后再遍历打印出来

我们也可以创建一个刚好可以放下map中key的slice

names :=make([]string,0,len(ages))

map类型的类型零值是nil,也就是没有引用任何哈希表

func main() {
   var ages map[string]int
   fmt.Println(ages == nil)//true
}

并且向一个nil值的map中存入元素将导致异常,在向map存数据时必须先创建map

ages["carol"] = 21   // 不支持赋值操作
ages["jane"]++       // 支持值本身变化操作

通过key作为索引下标来访问map将产生一个value,如果key在map中是存在的,那么将得到与key相对应的value;如果key不存在,那么将得到value对应的零值,无论存在不存在,这种操作都不会报错

ages := map[string]int{
   "bob":31,
   "tom":32,
   "john":33,
   "shein":34,
   "azazie":35,
}
fmt.Println(ages["tony"],ages["bob"])//0 31

如果元素类型是一个数字,你可以区分一个已经存在的0和不存在而返回的0值,可以像下面这样测试:

age, ok := ages["bob"]
if !ok {/*"bob" is not a key in this map; age == 0. */}

我们来测试一个值:jane

ages := map[string]int{
   "bob":31,
   "tom":32,
   "john":33,
   "shein":34,
   "azazie":35,
   "jane":0,//我们把jane这个元素放入
}
fmt.Println(ages["tony"],ages["bob"])//0 31
age,ok := ages["jane"]
if !ok{
   fmt.Printf("there is no name called jane,and now we created it,its age is %v",age)
}else{
   fmt.Printf("'jane' is in the ages, and it's age is %v",ages["jane"])//'jane' is in the ages, and it's age is 0 //bingo! 成功判断
}

你会经常看到将这两个结合起来使用:

if age,ok := ages["bob"];!ok {/*...*/}

map的比较

和slice一样,map之间也不能直接比较,唯一的例外是和nil比较,如果要判断,那得使用循环,用!ok来区分元素存不存在

func main() {
	x := map[string]int{"beijing":0,"shanghai":2,"guangzhou":3,"shenzhen":4,"chongqing":5}
	y := map[string]int{"beijing":1,"shanghai":2,"guangzhou":3,"shenzhen":4,"chongqing":5}

	fmt.Println(equal(x,y))//false
}
func equal(x,y map[string]int)bool{
	if len(x) != len(y){//第一步,先判断两个map的长度,若长度相同,则继续判断map中的key和value是否相等
		return false
	}
	for k,xv := range x {
		if yv,ok := y[k];!ok || yv != xv {//第二步,判断yv是否存在并且等于xv,如果不存在或者不等于xv,则不相等
			return false
		}
	}
	return true
}

在例子中,我们使用了!ok来区分元素不存在,与元素存在但为0。我们不能简单的用vx!=y[k]判断,那样会导致在判断下面两个map时产生错误结果

equal(map[string]int{"A":0},map[string]int{"B":42})

Go语言中并没有提供一个set类型,但是map中的key也是不相同的,可以用map实现类似set的功能

我们写一个程序,读取多行输入,但是只打印第一次出现的行

func main() {
   seen := make(map[string]bool)
   input := bufio.NewScanner(os.Stdin)
   for input.Scan() {
      line := input.Text()
      if !seen[line]{
         seen[line] = true
         fmt.Println(line)
      }
   }

   if err := input.Err();err!= nil {
      fmt.Fprintf(os.Stderr,"dedup:%v\n",err)
      os.Exit(1)
   }
}

我们将这种忽略value的map当作一个字符串集合,并非所有的map[string]bool类型value都是无关紧要的,有一些则可能同时包含true和false的值

有时候我们需要一个map或set的key是slice类型,但是map的key必须是可比较的类型,那我们就可以进行转换,如下:

var m = make(map[string]int)

func k(list []string) string {//将slice转换为map的string类型key
   return fmt.Sprintf("%q", list)
}
func Add(list []string) {
   m[k(list)]++
}
func Count(list []string) int {
   return m[k(list)]
}

使用这种方式就可以把一些不能比较的类型转换为可比较的key

我们再来看一个map应用实例:统计Unicode码点出现的次数

func main() {
   counts := make(map[rune]int)
   var utflen [utf8.UTFMax + 1]int
   invalid := 0

   in := bufio.NewReader(os.Stdin)
   for {
      r, n, err := in.ReadRune()
      if err == io.EOF {
         break
      }
      if err != nil {
         fmt.Fprintf(os.Stderr, "charcount:%v\n", err)
         os.Exit(1)
      }
      if r == unicode.ReplacementChar && n == 1 {
         invalid++
         continue
      }
      counts[r]++
      utflen[n]++
   }
   fmt.Printf("\nlen\tcount\n")
   for i, n := range utflen {
      if i > 0 {
         fmt.Printf("%d\t%d\n", i, n)
      }
   }
   if invalid > 0 {
      fmt.Printf("\n%d invalid UTF-8 characters\n", invalid)
   }
}

ReadRune方法执行的UTF-8解码并返回三个值,解码的rune字符的值,字符UTF-8编码后的长度,和一个错误值。我们可预期的错误值只有对应文件结尾的io.EOF.如果输入的是无效的UTF-8编码的字符,返回的是unicode.ReplacementChar表示无效字符,并且编码长度是1

map的value类型也可以是一个聚合类型,比如一个map或slice

var graph  = make(map[string]map[string]bool)
func addEdge(from, to string) {
   edges := graph[from]
   if edges == nil {
      edges = make(map[string]bool)
      graph[from] = edges
   }
   edges[to] = true
}
func hasEdge(from, to string)bool{
   return graph[from][to]
}

其中,addEdge 函数惰性初始化map是一个惯用的方式,也就是说在每个值首次作为key时才初始化,addEdge 函数显示了如何让map的零值他也能正常工作,即使from到to的边不存在,graph[from] [to]依然可以返回一个有意义的结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值