A Tour of Go メモ ~More types: structs, slices, and maps~

## はじめに
 
Go の勉強のため、[A Tour of Go](https://go-tour-jp.appspot.com/moretypes/1)に取り組んでいる
今回は`More types: structs, slices, and maps`章について、学んだことを記していく

まとめ記事は[こちら](https://qiita.com/eyuta/items/77ac58e4d9141898c733)

## 使えそうなスニペット

※筆者は VSCode を使用

### `tys`

名前、フィールドの順にカーソルが移動する

```go
type name struct {

}
```

### `forr`

インデックス、要素、イテレータの順にカーソルが移動する

```go
for _, var := range var {

}
```

### struct や map の初期化の際、型を補完してくれる

これはなかなか便利
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110860/ead7a08f-51f3-25fc-dbe6-c10a9df2affc.png)

## ページごとの補足

### Pointers

ポインタを理解するのに、[この記事](https://qiita.com/Sekky0905/items/447efa04a95e3fec217f)が参考になった
ざっくりした理解ではあるが、

- オペランド(変数および値)はメモリのどこかに格納されている
- ポインタとは、オペランドを格納しているメモリのアドレス
- `&`オペレータは、そのオペランドのポインタを示す
- `*`オペレータは、そのポインタの指す先の変数を示す

```go
i := 10
fmt.Println(i) // 10
fmt.Println(&i) // 0xc000012080
fmt.Println(*&i) // 10
```

### Arrays

#### 配列とは

Go の配列は値である
配列変数は配列全体を示す(C 言語とは違う)
配列は、 `名前付きフィールドではなくインデックスフィールドを使用できる構造体の一種`と考えると腹落ちしやすかった

#### コンパイラに配列の要素数をカウントさせる

```go
 // 以下の 2 つは同じ型になる
	a := [6]int{2, 3, 5, 7, 11, 13}
  b := [...]int{2, 3, 5, 7, 11, 13}
```

#### 初期値

初期化時に与えた引数より配列のサイズが大きい場合、ゼロ値が代入される

```go
primes := [10]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes) // [2 3 5 7 11 13 0 0 0 0]
```

### Slice length and capacity

[Go Slices: usage and internals](https://blog.golang.org/slices-intro)が非常に分かりやすい

スライスは、

- 配列へのポインタ
- スライスによって参照される要素の数
- 容量は、基になる配列の要素の数

で構成される、と考えると理解しやすかった

```go
a := [6]int{2, 3, 5, 7, 11, 13}
s := a[:]
printSlice(s) // len=6, cap=6, [2 3 5 7 11 13]

s = s[:0]     // 参照される要素の数を変更
printSlice(s) // len=0, cap=6, []

s = s[:4]     // 参照される要素の数を変更
printSlice(s) // len=4, cap=6, [2 3 5 7]

s = s[2:]     // ポインタを s[0] から s[2]に移動する == その分、参照できるlen, capが減る
printSlice(s) // len=2, cap=4, [5 7]

s = s[:4]     // ポインタを移動した状態で、参照される要素の数を変更
printSlice(s) // len=4, cap=4, [5 7 11 13]

func printSlice(s []int) {
	fmt.Printf("len=%d, cap=%d, %v\n", len(s), cap(s), s)
}
```

スライスの容量を越えて延ばすと、`panic: runtime error: slice bounds out of range [:5] with capacity 4`が発生する

### Creating a slice with make

slice は`make`関数でも定義できる
定義: `func make([]T, len, cap) []T`
容量(cap)を省略すると、長さ(len)と同じになる

```go
// 同じ値を定義
fmt.Println(make([]int, 5, 5))
fmt.Println(make([]int, 5))
fmt.Println([]int{0, 0, 0, 0, 0})

```

### Slices of slices

サンプル通りに書くと、`redundant type from array, slice, or map composite`と警告が出る
入れ子の場合は内部の型を省略できる

```go
// not very well
board := [][]string{
  []string{"_", "_", "_"},
  []string{"_", "_", "_"},
  []string{"_", "_", "_"},
}
// well
board := [][]string{
  {"_", "_", "_"},
  {"_", "_", "_"},
  {"_", "_", "_"},
}
```

### Appending to a slice

もし、元の配列 s が、変数群を追加する際に容量が小さい場合は、より大きいサイズの配列を割り当て直す
再割当てにはコストがかかるため、予め多くを append することが分かっているなら、必要な分だけ容量を確保したほうが良い

```go
var s []int // 20ms: 初期容量は0
s = make([]int, 0, 1000000) // 7ms: 初期容量は1000000
for i := 0; i < 1000000; i++ {
  s = append(s, 0)
}
```

ちなみに、時間測定の方法は[こちら](https://stackoverflow.com/questions/45766572/is-there-an-efficient-way-to-calculate-execution-time-in-golang)が参考になった

```go
func elapsed() func() {
	start := time.Now()
	return func() {
		fmt.Printf("%v", time.Since(start))
	}

}
func main() {
	defer elapsed()()
}
```

### Exercise: Slices

#### パッケージインストール

手元で動かす場合は `"golang.org/x/tour/pic"`をインストール必要がある
そのまま get しても良いが、`Go Module`を使ってローカルに get してみた
Ref. [新規プロジェクトの作りかたがわからない](https://qiita.com/mumoshu/items/0d2f2a13c6e9fc8da2a4#%E6%96%B0%E8%A6%8F%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%BD%9C%E3%82%8A%E3%81%8B%E3%81%9F%E3%81%8C%E3%82%8F%E3%81%8B%E3%82%89%E3%81%AA%E3%81%84)

```sh
$ go mod init {PROJECT_NAME}
 go get golang.org/x/tour
```

こうすると、`go.mod`が作成され、get したパッケージのバージョン情報が記述される

```mod
module github.com/eyuta/goTour

go 1.15

require golang.org/x/tour v0.0.0-20201207214521-004403599411
```

#### 実装

インデックスで挿入する場合は、

```go
func Pic(dx, dy int) [][]uint8 {
	exp := make([][]uint8, dx)
	for i := 0; i < dx; i++ {
		inner := make([]uint8, dy)
		for j := 0; j < dy; j++ {
			inner[j] = uint8(i * j)
		}
		exp[i] = inner
	}
	return exp
}
```

append を使う場合、

```go
func Pic(dx, dy int) [][]uint8 {
	exp := make([][]uint8, 0, dx)
	for i := 0; i < dx; i++ {
		inner := make([]uint8, 0, dy)
		for j := 0; j < dy; j++ {
			inner = append(inner, uint8(i*j))
		}
		exp = append(exp, inner)
	}
	return exp
}
```

尚、スライスの定義を`[][]uint8{}`といったような容量を 0 にして定義した場合、若干速度が遅くなる
(`Appending to a slice`参照)

```log
初期容量=dx, dy: 22ms ~ 24ms
初期容量=0 : 24ms ~ 28ms
```

インデックスと append では速度の差はほとんどなかった

### Exercise: Maps

`make`するの忘れがちなので注意する

```go
func WordCount(s string) (result map[string]int) {
	result = make(map[string]int)
	for _, field := range strings.Fields(s) {
		elem, ok := result[field]
		switch ok {
		case true:
			result[field] = elem + 1
		case false:
			result[field] = 1
		}
	}
	return
}
```

### Exercise: Fibonacci closure

```go
func fibonacci() func() int {
	x, y := 0, 1
	return func() int {
		result := x
		x, y = y, x+y
		return result
	}
}
```