回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,16 +1,16 @@
## 4.3. Map
哈希表是一巧妙併且實用的數據結構。它是一個無序的key/value的集合其中所有的key都是不同的後通過給定的key可以在常數時間複雜度內檢索、更新或刪除對應的value。
哈希表是一巧妙并且实用的数据结构。它是一个无序的key/value的集合其中所有的key都是不同的后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。
在Go言中,一map就是一哈希表的引用map型可以寫爲map[K]V其中K和V分别對應key和value。map中所有的key都有相同的所有的value也有着相同的但是key和value之可以是不同的數據類型。其中K對應的key必是支持==比較運算符的數據類所以map可以通過測試key是否相等來判斷是否已存在。然浮點數類型也是支持相等算符比的,但是將浮點數用做key類型則是一個壞的想法,正如第三章提到的,最的情是可能出的NaN和任何浮點數都不相等。對於V對應的value數據類型則沒有任何的限
在Go言中,一map就是一哈希表的引用map型可以写为map[K]V其中K和V分别对应key和value。map中所有的key都有相同的所有的value也有着相同的但是key和value之可以是不同的数据类型。其中K对应的key必是支持==比较运算符的数据类所以map可以通过测试key是否相等来判断是否已存在。然浮点数类型也是支持相等算符比的,但是将浮点数用做key类型则是一个坏的想法,正如第三章提到的,最的情是可能出的NaN和任何浮点数都不相等。对于V对应的value数据类型则没有任何的限
置的make函可以建一map
置的make函可以建一map
```Go
ages := make(map[string]int) // mapping from strings to ints
```
也可以用map字面值的語法創建map時還可以指定一些最初的key/value
也可以用map字面值的语法创建map时还可以指定一些最初的key/value
```Go
ages := map[string]int{
@@ -19,7 +19,7 @@ ages := map[string]int{
}
```
這相當於
这相当于
```Go
ages := make(map[string]int)
@@ -27,48 +27,48 @@ ages["alice"] = 31
ages["charlie"] = 34
```
因此,另一種創建空的map的表式是`map[string]int{}`
因此,另一种创建空的map的表式是`map[string]int{}`
Map中的元素通key對應的下標語法訪問
Map中的元素通key对应的下标语法访问
```Go
ages["alice"] = 32
fmt.Println(ages["alice"]) // "32"
```
使用置的delete函可以除元素:
使用置的delete函可以除元素:
```Go
delete(ages, "alice") // remove element ages["alice"]
```
所有些操作是安全的,卽使這些元素不在map中也沒有關繫;如果一個査找失敗將返迴value類型對應的零值,例如,使map中不存在“bob”下面的代也可以正常工作,因ages["bob"]失敗時將返迴0。
所有些操作是安全的,即使这些元素不在map中也没有关系;如果一个查找失败将返回value类型对应的零值,例如,使map中不存在“bob”下面的代也可以正常工作,因ages["bob"]失败时将返回0。
```Go
ages["bob"] = ages["bob"] + 1 // happy birthday!
```
而且`x += y``x++`簡短賦值語法也可以用在map上所以上面的代可以改
而且`x += y``x++`简短赋值语法也可以用在map上所以上面的代可以改
```Go
ages["bob"] += 1
```
簡單的寫
简单的写
```Go
ages["bob"]++
```
但是map中的元素不是一個變量,因此我不能map的元素行取址操作:
但是map中的元素不是一个变量,因此我不能map的元素行取址操作:
```Go
_ = &ages["bob"] // compile error: cannot take address of map element
```
禁止map元素取址的原因是map可能着元素量的增而重新分配更大的存空間,從而可能致之前的地址效。
禁止map元素取址的原因是map可能着元素量的增而重新分配更大的存空间,从而可能致之前的地址效。
要想遍map中全部的key/value對的話可以使用range格的for循環實現和之前的slice遍歷語法類似。下面的迭代語句將在每次迭代時設置name和age量,它們對應下一個鍵/值
要想遍map中全部的key/value对的话可以使用range格的for循环实现和之前的slice遍历语法类似。下面的迭代语句将在每次迭代时设置name和age量,它们对应下一个键/值
```Go
for name, age := range ages {
@@ -76,7 +76,7 @@ for name, age := range ages {
}
```
Map的迭代序是不定的,且不同的哈希函數實現可能致不同的遍歷順序。在實踐中,遍歷的順序是隨機的,每一次遍歷的順序都不相同。是故意的,每次都使用隨機的遍歷順序可以強製要求程序不會依賴具體的哈希函數實現。如果要按序遍key/value,我們必須顯式地key行排序可以使用sort包的Strings函數對字符串slice行排序。下面是常見的處理方式:
Map的迭代序是不定的,且不同的哈希函数实现可能致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同。是故意的,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。如果要按序遍key/value,我们必须显式地key行排序可以使用sort包的Strings函数对字符串slice行排序。下面是常见的处理方式:
```Go
import "sort"
@@ -91,15 +91,15 @@ for _, name := range names {
}
```
爲我們一開始就知道names的最大小,因此slice分配一個合適的大小將會更有效。下面的代碼創建了一空的slice但是slice的容量好可以放下map中全部的key
为我们一开始就知道names的最大小,因此slice分配一个合适的大小将会更有效。下面的代码创建了一空的slice但是slice的容量好可以放下map中全部的key
```Go
names := make([]string, 0, len(ages))
```
在上面的第一range循中,我們隻關心map中的key所以我忽略了第二個循環變量。在第二個循環中,我們隻關心names中的名字所以我使用“_”空白標識符來忽略第一個循環變也就是迭代slice的索引。
在上面的第一range循中,我们只关心map中的key所以我忽略了第二个循环变量。在第二个循环中,我们只关心names中的名字所以我使用“_”空白标识符来忽略第一个循环变也就是迭代slice的索引。
map型的零值是nil也就是有引用任何哈希表。
map型的零值是nil也就是有引用任何哈希表。
```Go
var ages map[string]int
@@ -107,30 +107,30 @@ fmt.Println(ages == nil) // "true"
fmt.Println(len(ages) == 0) // "true"
```
map上的大部分操作包括找、除、len和range循都可以安全工作在nil值的map上的行和一空的map似。但是向一nil值的map存入元素將導致一panic常:
map上的大部分操作包括找、除、len和range循都可以安全工作在nil值的map上的行和一空的map似。但是向一nil值的map存入元素将导致一panic常:
```Go
ages["carol"] = 21 // panic: assignment to entry in nil map
```
在向map存數據前必須先創建map。
在向map存数据前必须先创建map。
key作索引下標來訪問map將産生一value。如果key在map中是存在的麽將得到key對應的value如果key不存在麽將得到value對應類型的零值,正如我前面看到的ages["bob"]那樣。這個規則很實用,但是有候可能需要知道對應的元素是否的是在map之中。例如如果元素型是一個數字,你可以需要分一個已經存在的0和不存在而返零值的0可以像下面這樣測試
key作索引下标来访问map将产生一value。如果key在map中是存在的么将得到key对应的value如果key不存在么将得到value对应类型的零值,正如我前面看到的ages["bob"]那样。这个规则很实用,但是有候可能需要知道对应的元素是否的是在map之中。例如如果元素型是一个数字,你可以需要分一个已经存在的0和不存在而返零值的0可以像下面这样测试
```Go
age, ok := ages["bob"]
if !ok { /* "bob" is not a key in this map; age == 0. */ }
```
會經常看到將這兩個結合起使用,像這樣
会经常看到将这两个结合起使用,像这样
```Go
if age, ok := ages["bob"]; !ok { /* ... */ }
```
這種場景下map的下標語法將産生兩個值;第二是一個布爾值,用於報告元素是否的存在。布爾變量一般命名ok特别適合馬上用if件判部分。
这种场景下map的下标语法将产生两个值;第二是一个布尔值,用于报告元素是否的存在。布尔变量一般命名ok特别适合马上用if件判部分。
和slice一map之也不能行相等比唯一的例外是和nil行比。要判斷兩個map是否包含相同的key和value們必須通過一個循環實現
和slice一map之也不能行相等比唯一的例外是和nil行比。要判断两个map是否包含相同的key和value们必须通过一个循环实现
```Go
func equal(x, y map[string]int) bool {
@@ -146,14 +146,14 @@ func equal(x, y map[string]int) bool {
}
```
意我是如何用!ok來區分元素缺失和元素不同的。我不能簡單地用xv != y[k]判,那樣會導致在判下面兩個map時産生錯誤的結果:
意我是如何用!ok来区分元素缺失和元素不同的。我不能简单地用xv != y[k]判,那样会导致在判下面两个map时产生错误的结果:
```Go
// True if equal is written incorrectly.
equal(map[string]int{"A": 0}, map[string]int{"B": 42})
```
Go言中併沒有提供一set但是map中的key也是不相同的可以用map實現類似set的功能。爲了説明這一點下面的dedup程序取多行入,但是打印第一次出的行。它是1.3中出的dup程序的變體dedup程序通map表示所有的入行所對應的set集合保已在集合存在的行不被重打印。
Go言中并没有提供一set但是map中的key也是不相同的可以用map实现类似set的功能。为了说明这一点下面的dedup程序取多行入,但是打印第一次出的行。它是1.3中出的dup程序的变体dedup程序通map表示所有的入行所对应的set集合保已在集合存在的行不被重打印。
<u><i>gopl.io/ch4/dedup</i></u>
```Go
@@ -175,11 +175,11 @@ func main() {
}
```
Go程序員將這種忽略value的map作一字符串集合,非所有`map[string]bool`型value都是無關緊要的;有一些可能會同時包含true和false的值。
Go程序员将这种忽略value的map作一字符串集合,非所有`map[string]bool`型value都是无关紧要的;有一些可能会同时包含true和false的值。
候我需要一map或set的key是slice但是map的key必是可比較的類但是slice併不滿足這個條件。不,我可以通過兩個步驟繞過這個限製。第一步,定義一個輔助函kslice轉爲map對應的string型的key確保隻有x和y相等k(x) == k(y)才成立。然後創建一keystring型的map在每次map操作先用k助函數將slice轉化爲string型。
候我需要一map或set的key是slice但是map的key必是可比较的类但是slice并不满足这个条件。不,我可以通过两个步骤绕过这个限制。第一步,定义一个辅助函kslice转为map对应的string型的key确保只有x和y相等k(x) == k(y)才成立。然后创建一keystring型的map在每次map操作先用k助函数将slice转化为string型。
下面的例子演示了如何使用map來記録提交相同的字符串列表的次。它使用了fmt.Sprintf函數將字符串列表轉換爲一個字符串以用map的key%q參數忠實地記録每個字符串元素的信息:
下面的例子演示了如何使用map来记录提交相同的字符串列表的次。它使用了fmt.Sprintf函数将字符串列表转换为一个字符串以用map的key%q参数忠实地记录每个字符串元素的信息:
```Go
var m = make(map[string]int)
@@ -190,9 +190,9 @@ func Add(list []string) { m[k(list)]++ }
func Count(list []string) int { return m[k(list)] }
```
使用同的技可以理任何不可比的key型,而不僅僅是slice型。這種技術對於想使用自定key比較函數的時候也很有用,例如在比字符串的候忽略大小。同時,輔助函k(x)也不一定是字符串型,它可以返任何可比較的類型,例如整數、數組或結構體等。
使用同的技可以理任何不可比的key型,而不仅仅是slice型。这种技术对于想使用自定key比较函数的时候也很有用,例如在比字符串的候忽略大小。同时,辅助函k(x)也不一定是字符串型,它可以返任何可比较的类型,例如整数、数组或结构体等。
是map的另一例子,下面的程序用於統計輸入中每Unicode碼點出現的次數。雖然Unicode全部碼點的數量鉅大,但是出在特定文中的字符種類併沒有多少使用map可以用比自然的方式來跟蹤那些出現過字符的次
是map的另一例子,下面的程序用于统计输入中每Unicode码点出现的次数。虽然Unicode全部码点的数量巨大,但是出在特定文中的字符种类并没有多少使用map可以用比自然的方式来跟踪那些出现过字符的次
<u><i>gopl.io/ch4/charcount</i></u>
```Go
@@ -246,15 +246,15 @@ func main() {
}
```
ReadRune方法行UTF-8解碼併返迴三個值:解的rune字符的值字符UTF-8編碼後的長度,和一個錯誤值。我們可預期的錯誤值隻有對應文件尾的io.EOF。如果入的是效的UTF-8編碼的字符,返迴的將是unicode.ReplacementChar表示效字符,併且編碼長度是1。
ReadRune方法行UTF-8解码并返回三个值:解的rune字符的值字符UTF-8编码后的长度,和一个错误值。我们可预期的错误值只有对应文件尾的io.EOF。如果入的是效的UTF-8编码的字符,返回的将是unicode.ReplacementChar表示效字符,并且编码长度是1。
charcount程序同打印不同UTF-8編碼長度的字符目。map不是一個合適的數據結構;因UTF-8編碼的長度總是從1到utf8.UTFMax最大是4個字節),使用數組將更有效。
charcount程序同打印不同UTF-8编码长度的字符目。map不是一个合适的数据结构;因UTF-8编码的长度总是从1到utf8.UTFMax最大是4个字节),使用数组将更有效。
爲一個實驗,我用charcount程序英文版原稿的字符行了統計。雖然大部分是英但是也有一些非ASCII字符。下面是排名前10的非ASCII字符
为一个实验,我用charcount程序英文版原稿的字符行了统计。虽然大部分是英但是也有一些非ASCII字符。下面是排名前10的非ASCII字符
![](../images/ch4-xx-01.png)
下面是不同UTF-8編碼長度的字符的目:
下面是不同UTF-8编码长度的字符的目:
```
len count
@@ -264,7 +264,7 @@ len count
4 0
```
Map的value型也可以是一聚合型,比如是一map或slice。在下面的代中,graph的key型是一字符串value型map[string]bool代表一字符串集合。概念上graph將一個字符串型的key映射到一組相關的字符串集合,它指向新的graph的key。
Map的value型也可以是一聚合型,比如是一map或slice。在下面的代中,graph的key型是一字符串value型map[string]bool代表一字符串集合。概念上graph将一个字符串型的key映射到一组相关的字符串集合,它指向新的graph的key。
<u><i>gopl.io/ch4/graph</i></u>
```Go
@@ -284,8 +284,8 @@ func hasEdge(from, to string) bool {
}
```
其中addEdge函惰性初始化map是一個慣用方式,也就是在每值首次作key才初始化。addEdge函數顯示了如何map的零值也能正常工作使from到to的不存在graph[from][to]依然可以返迴一個有意義的結果。
其中addEdge函惰性初始化map是一个惯用方式,也就是在每值首次作key才初始化。addEdge函数显示了如何map的零值也能正常工作使from到to的不存在graph[from][to]依然可以返回一个有意义的结果。
**練習 4.8** 改charcount程序使用unicode.IsLetter等相的函數,統計字母、字等Unicode中不同的字符别。
**练习 4.8** 改charcount程序使用unicode.IsLetter等相的函数,统计字母、字等Unicode中不同的字符别。
**練習 4.9** 編寫一個程序wordfreq程序報告輸入文本中每個單詞出現的頻率。在第一次調用Scan前先調用input.Split(bufio.ScanWords)函數,這樣可以按單詞而不是按行入。
**练习 4.9** 编写一个程序wordfreq程序报告输入文本中每个单词出现的频率。在第一次用Scan前先用input.Split(bufio.ScanWords)函数,这样可以按单词而不是按行入。