สรุปการจัดการข้อมูลแบบ slice ของ Go
Tags: go, programming
Published: 2020-12-07 03:55:22

Slice เป็นข้อมูลสำหรับเก็บของที่เป็นค่าเดียวกันหลายๆค่า เราใช้ Slice เยอะมากใน Go โพสต์นี้จะรวบรวมสรุปสิ่งที่เราจัดการ Slice ของ Go เอาไว้ ตั้งแต่การสร้าง, การจัดการสมาชิก รวมถึงจัดการหน่วยความจำของสมาชิก แม้ตัวอย่างจะเป็นแค่ slice ของ int แต่ก็สามารถใช้กับ slice ที่สมาชิกเป็น type อื่นๆได้เช่นกัน

### 1) Creates `nil` slice (zero value of slice)

ถ้าเราประกาศตัวแปรประเภท slice ขึ้นมาเฉยๆค่ามันจะเป็น `nil` ซึ่งหมายถึงตัวแปรนี้ยังไม่มีส่วนของพื้นที่สำหรับเก็บ element ใดๆ ดังนั้นถ้าเอาไปหา length ด้วยฟังก์ชัน `len` ก็จะได้ 0
ตัวอย่างเช่น

```go
// s == nil, len(s) == 0

var s []int
```

### 2) Creates empty (0 element) slice

empty slice ต่างจาก slice ที่มีค่า `nil` คือมีการสร้างส่วนสำหรับเก็บ element แล้วแต่ยังไม่มี element ใดๆอยู่ ถ้าเอาไปหาด้วย `len` ก็ได้ 0 เช่นกัน
ตัวอย่างเช่น

```go
// s == []int{}, len(s) == 0

var s []int
s = []int{}

// หรือถ้าต้องการสร้าง s พร้อมกับกำหนดค่าให้เป็น empty []int{} ด้วย := เลยก็ได้

s := []int{}

// หรือแบบนี้ถ้าเป็น package scope

var s = []int{}

// หรือใช้ฟังก์ชัน make ช่วยแทนที่จะกำหนดด้วย []int{} ก็ได้ โดยกำหนดจำนวน element ที่ต้องการเป็น 0

s := make([]int, 0)
```

### 3) Creates n zero value elements slice

เราสามารถใช้ make เพื่อเตรียม slot สำหรับเก็บ element ไว้ล่วงหน้าได้โดยใช้ make แล้วกำหนดจำนวน element ที่ต้องการโดยที่เราจะได้ slice ที่มี element ตามจำนวนที่กำหนดแต่ว่าทุกค่าจะเป็น zero value ของ type ของ element
ตัวอย่างเช่น

```go
// s จะได้สมาชิกเป็น int 5 ตัวโดยที่ทุกค่าเป็น 0

s := make([]int, 5)
```

### 4) Creates n elements slice

ถ้าเราต้องการสร้าง slice ที่มีสมาชิก n ตัวเริ่มต้นที่ค่าต่างกัน เราใช้ slice literal ได้ซึ่งเราทำไปแล้วแบบนี้ `[]int{}` เพื่อกำหนด empty slice ถ้าเราอยากได้สมาชิกเริ่มต้นเท่าไหร่ก็ใส่ไปในปีกกาคั่นด้วย comma ได้เลย
ตัวอย่างเช่น

```go
// s จะได้สมาชิกเป็น int 5 ตัวตามที่เราใส่ใน `{}` เลย

s := []int{1, 2, 3, 4, 5}
```

### 5) Access (index started from 0)

การเข้าถึง element ของ slice จะเป็นแบบ index base โดยที่ index เป็นจำนวนเต็ม เริ่มต้นที่ 0 แล้วนับไปทีละ 1
ตัวอย่างเช่น

```go
s := []int{5, 6, 7, 8, 9}

// s[2] == 7

fmt.Println(s[2])

// panic เพราะเข้าถึง index ที่ไม่มีอยู่ของ slice นี้

fmt.Println(s[5])

// index เป็นได้ทั้งตัวเลขโดยตรงหรือจากตัวแปรที่เป็นจำนวนเต็มก็ได้

i := 3

// s[i] == 8
fmt.Println(s[i])
```

### 6) Iterate each element (for counter index, for range)

เราสามารถใช้ `for` เพื่อวนซ้ำตามจำนวน element ของ slice ได้โดยเขียนได้ทั้งแบบ counter index ตาม length หรือจะใช้ `for range` ช่วยก็ได้
ตัวอย่างเช่น

```go
s := []int{1, 2, 3, 4}

// ใช้ for loop นับ index ไปจาก 0 จนถึงตัวสุดท้ายคือตัวที่น้อยกว่า len(s) อยู่ 1

for i := 0; i < len(s); i++ {
        fmt.Println(s[i])
}

// ใช้ for range ซึ่งจะวนซ้ำจากตัวแรกจนถึงตัวสุดท้ายโดยให้ range กำหนดค่า index `i` ให้ในแต่ละรอบ

for i := range s {
        fmt.Println(s[i])
}

// ใช้ for range ซึ่งจะวนซ้ำจากตัวแรกจนถึงตัวสุดท้ายโดยให้ range กำหนดค่า index `i` ให้ในแต่ละรอบและค่าของ element ในตำแหน่งที่ s[i] ให้ตัวแปร v ให้ในแต่ละรอบ

for i, v := range s {
        fmt.Println(i, v)
}

// แต่ถ้าเราไม่ได้ใช้ index ให้ใช้ _ แทนชื่อตัวแปรได้เช่น

for _, v := range s {
        fmt.Println(v)
}

```

### 7) Update element value at index

เราสามารถแก้ไขค่าของสมาชิกของ slice ในตำแหน่งที่ต้องการได้โดยใช้ operator `=` แล้วกำหนดค่าที่ต้องการให้กับ s ในตำแหน่งที่ต้องการโดยใช้การอ้างอิงตำแหน่งแบบเดียวกับตอนเข้าถึงค่า
ตัวอย่างเช่น

```go
s := []int{1, 2, 3, 4, 5}

// แก้ไขค่า s ใน index ที่ 2 เป็น 9

s[2] = 9

// แก้ไขค่า s โดยแก้ในตำแหน่งที่ตัวแปร i เก็บค่าไว้เช่น i = 4, s[4] ก็เปลี่ยนเป็น 7

i := 4
s[i] = 7

// panic ถ้าเราแก้ไขในตำแหน่งที่ไม่มีอยู่ของ slice

s[5] = 10
```

### 8) Multiple slices share element

ตัวแปร slice คนละตัว สามารถแชร์ element ร่วมกันได้ ตราบใดที่ยังไม่มีการเปลี่ยนแปลงหน่วยความจำที่จัดเก็บ element ภายในของ slice ซึ่งอาจจะเปลี่ยนได้ถ้ามีการสร้าง slice ใหม่ หรือมีการเพิ่มจำนวนสมาชิกจนทำให้ต้องสร้างหน่วยความจำใหม่เพื่อให้เพียงพอกับสมาชิกที่เพิ่มขึ้นมา
ตัวอย่างเช่น

```go
s := []int{1, 2, 3, 4, 5}

// กำหนด s ให้กับตัวแปร slice ใหม่ จะทำให้แชร์ element ร่วมกัน ตราบใดที่ยังไม่มีการเปลี่ยนแปลงของหน่วยความจำภายในที่จัดเก็บ element

ss := s

// ดังนั้นถ้าเราแก้ไขค่าสมาชิกผ่าน s หรือ ss พอเข้าถึงค่าในตำแหน่งนั้นก็จะได้ค่าเท่ากัน

s[1] = 6

// ss[1] ก็ได้ 6 เช่นกัน
fmt.Println(ss[1])

ss[2] = 7

// s[2] ก็ได้ 7 เช่นกัน
fmt.Println(s[2])

```

### 9) Share with slice function parameter

ด้วยวิธีการแชร์ element ร่วมกันของ slice ทำให้เราจะเห็นโค้ดในการจัดการ element ของ slice แยกออกเป็น function ที่รับ slice แล้วใน function นั้นจะทำการ update ค่าของ element เป็นค่าที่ต้องการให้ เช่น

```go
func plusAllElementWithFive(nums []int) {
        for i := range nums {
                nums[i] = nums[i] + 5
        }
}

func main() {
        s := []int{1, 2, 3, 4, 5}
        plusAllElementWithFive(s)

        // สมาชิกใน s จะถูกบวกไปอีก 5 , s จะได้เป็น [6, 7, 8, 9, 10]

        fmt.Println(s)
}
```

อย่างไรก็ตามเทคนิคนี้เราจะให้กับ function ที่อ่านค่าของใน element หรือแก้ไขค่าใน element เท่านั้น เพราะ slice แชร์แค่ element กันอยู่ แต่ตัวแปร slice คือว่าเป็นคนละตัว ถ้าเราเปลี่ยนตัวแปร slice เป็น slice อื่น ตัวแปรที่ส่งมาจะไม่เปลี่ยนไปด้วย เช่น

```go
func cannotChangeCallerSlice(s []int) {
        s = []int{5, 6, 7, 8}
}

func main() {
        s := []int{1, 2, 3, 4}

        // s ไม่เปลี่ยนเป็น [5, 6, 7, 8] เพราะถือว่า s เป็นคนละตัวกัน

        cannotChangeCallerSlice(s)

        // s ยังคงเป็น [1, 2, 3, 4]
        fmt.Println(s)
}
```

เทคนิคนี้ Go standard package `io` ก็ใช้กับ interface Reader กับ interface Write เช่นกันที่รับ slice ของ byte โดยที่ Reader จะรับ `[]byte` ไปแล้วอ่านค่ามาอัพเดทสมาชิกของ `[]byte` ที่ส่งไป ส่วน Writer เอาค่าสมาชิกของ `[]byte` ที่ส่งไปไปเขียนลงปลายทาง

```
type Reader interface {
        Read(b []byte) (n int, err error)
}

type Writer interface {
        Write(b []byte) (n int, err error)
}
```

### 10) Slices a slice

เราสามารถแชร์ สมาชิกของ slice โดยกำหนดให้แชร์แค่บางส่วน ไม่ใช่ทั้งหมดได้โดยใช้ slice operator
ตัวอย่างเช่น

```go
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

// เราต้องการแชร์ค่าของ s จาก index ที่ 1 ถึง 4 โดยกำหนดเลข 1 คั่นด้วย : แล้ว 5 ใน []
// จะเห็นว่าถ้าเราต้องการถึงตำแหน่งที่ n เราต้องกำหนดค่าหลังเป็น n+1 เช่นต้องการ 4 กำหนดเป็น 5

ss := s[1:5]

// สิ่งที่จะได้คือ ss ที่มีสมาชิก 4 ตัวเริ่มที่ 0 ถึง 3 โดยที่มีค่าเป็น [2, 3, 4, 5]

fmt.Println(ss)

// ss แชร์สมาชิกกับ s อยู่ถ้าเราเปลี่ยน s ในตำแหน่งที่ 1 ถึง 4 ค่าของ ss ตำแหน่งที่ 0 ถึง 3 ก็เปลี่ยนด้วยตามลำดับเช่น

s[1] = 20

// ss[0] เป็น 20 ด้วย
fmt.Println(ss[0])

ss[1] = 30

// s[2] เป็น 30 ด้วย
fmt.Println(s[2])
```

### 11) Append new element to slice

การเพิ่มสมาชิกของ slice จะใช้ function `append` เราสามารถเพิ่มทีละ 1 ค่า หรือหลายค่าพร้อมกันได้เช่น

```go
s := []int{1, 2}

// เริ่มค่า 3 ให้ s เวลาเรียก append ต้องรับค่ามากำหนดให้กับ s ตัวเดิมเสมอ เดี๋ยวอธิบายเพิ่มเติมในเรื่อง maintain capability storage

s = append(s, 3)

// สมาชิกของ s ตอนนี้เป็น [1, 2, 3]
fmt.Println(s)

// เพิ่มหลายค่าได้ เนื่องจาก append รับ variadic argument

s = append(s, 4, 5, 6, 7)

// สมาชิกของ s ตอนนี้เป็น [1, 2, 3, 4, 5, 6, 7]
fmt.Println(s)

// เรายังส่งค่าจากอีก slice เข้าไป append ได้ด้วยท่านี้ เพราะเป็น variadic argument

ss := []int{8, 9, 10}

s = append(s, ss...)

// สมาชิกของ s ตอนนี้เป็น [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
fmt.Println(s)
```

### 12) Prepend element to slice

เราสามารถใช้ `append` ช่วยเพิ่มสมาชิกไปด้านหน้าของ slice ได้เช่นกัน อาศัยเทคนิคเล็กน้อยคือกำหนดค่าแรกเป็น slice ที่มีสมาชิกเดียวที่เราต้องการเพิ่มแล้วใช่ท่ากระจายสมาชิกของ slice เป็น variadic argument เช่น

```go
s := []int{1, 2, 3}

// เพิ่ม 4 ไปด้านหน้า s โดยใช้ append แบบนี้

s = append([]int{4}, s...)

// ตอนนี้สมาชิกใน s เป็น [4, 1, 2, 3]
fmt.Println(s)
```

### 13) Copy slice without share element

เรารู้ไปแล้วว่าถ้าเรากำหนดค่าตัวแปร slice ให้กับอีกตัวแปร มันจะแชร์ element กัน แต่ถ้าเราอยาก copy slice โดย copy element ให้แทนที่จะแชร์กัน เราสามารถใช้ function `copy` ช่วยได้ เช่น

```go
s := []int{1, 2, 3, 4, 5}

// ถ้าเราต้องการ copy s เป็นอีกก้อนโดยไม่แชร์ element กันเราต้องเตรียม slice อีกก้อนขนาดเท่ากันขึ้นมาก่อน ในที่นี้จะใช้ make ช่วยง่ายๆแบบนี้

ss := make([]int, len(s))

// แล้วใช้ copy จาก s มาหา ss

copy(ss, s)

// ตอนนี้ ss จะเป็น [1, 2, 3, 4, 5]
fmt.Println(ss)

// แม้ว่าเปลี่ยนสมาชิกของ ss ค่าของ s ก็ไม่เปลี่ยนตามแล้ว

ss[0] = 7

// s[0] ยังคงเป็น 1
fmt.Println(s[0])
```

### 14) Maintain slice capability storage

slice นั้นจะมี storage สำหรับเก็บจำนวนสมาชิกซึ่งจะถูกจองหน่วยความจำไว้จำนวนหนึ่ง ซึ่งสำหรับ Go จะเรียกขนาดของหน่วยความจำที่จองไว้ว่า capacity เราสามารถใช้ function `cap` หาขนาดตรงนี้ได้เช่น

```go
s := []int{1, 2, 3, 4, 5}

// len(s) เท่ากับ 5

fmt.Println(len(s))

// ตอนนี้ cap(s) เท่ากับ 5 เช่นกัน

fmt.Println(cap(s))
```

ถ้าเกิดเราใช้ append เพิ่มค่าให้ slice แล้ว capacity ไม่เพียงพอ สิ่งที่ append จะทำคือจะจองหน่วยความจำใหม่ให้ 2 เท่าจากของ capacity เดิมแล้วค่อยเพิ่มสมาชิกเข้าไป เช่น

```go
s := []int{1, 2, 3}

// len(s) เท่ากับ 3 และ cap(s) เท่ากับ 3

fmt.Println(len(s))
fmt.Println(cap(s))

s = append(s, 4)

// len(s) เท่ากับ 4 และ cap(s) เท่ากับ 6

fmt.Println(len(s))
fmt.Println(cap(s))

```

นี่เป็นเหตุที่เราต้องกำหนดค่าที่ append return กลับมาให้กับตัวแปร slice ที่เราต้องการเพิ่มค่า เพราะเมื่อไหร่ที่ append สร้างหน่วยความจำใหม่ มันจะไม่สามารถแก้ไขค่าของ slice ที่ส่งไปได้ มันจะ return slice ตัวใหม่กลับมาให้แทน

เช่นถ้าเราไม่กำหนดค่าที่ append ส่งกลับมาให้ s จะทำให้ s ไม่มีอะไรเปลี่ยนแปลงหลัง append

```go
s := []int{1, 2, 3}

// len(s) เท่ากับ 3 และ cap(s) เท่ากับ 3

fmt.Println(len(s))
fmt.Println(cap(s))

_ = append(s, 4)

// len(s) จะยังเท่ากับ 3 และ cap(s) เท่ากับ 3

fmt.Println(len(s))
fmt.Println(cap(s))

// s ยังคงเป็น [1, 2, 3]

fmt.Println(s)
```

เราสามารถใช้ make เพื่อเตรียม slice ที่มียังไม่มีสมาชิก แต่เตรียม capacity เอาไว้รอได้เช่น

```go
// เตรียม capacity เอาไว้ 5 โดยใส่จำนวนที่ต้องการเป็น argument ที่ 3 ของ make

s := make([]int, 0, 5)

// len(s) เท่ากับ 0 และ cap(s) เท่ากับ 5

fmt.Println(len(s))
fmt.Println(cap(s))

// ดังนั้นเราสามารถ append ค่าของ s ได้โดยที่ cap ไม่สร้างใหม่จำกว่าจะไม่พอ

s = append(s, 1, 2, 3, 4)

// len(s) เท่ากับ 4 และ cap(s) เท่ากับ 5

fmt.Println(len(s))
fmt.Println(cap(s))
```


<a href="https://www.buymeacoffee.com/weerasak" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>