學習軌道
/
Go
Go
/
練習
/
紙牌魔術
紙牌魔術

紙牌魔術

學習練習

簡介

切片

Go 的切片類似於其他語言的陣列。 切片可以存放多個特定型別(或介面)的元素。

Go 的切片是以陣列為基礎。 陣列的大小是固定的。 相對地,切片則是動態調整大小、具彈性的陣列元素檢視。

切片的寫法類似[]T,其中T是切片中元素的型別:

var empty []int                 // an empty slice
withData := []int{0,1,2,3,4,5}  // a slice pre-filled with some data

你可以使用方括號語法,取得或設定指定以 0 為起算索引的元素:

withData[1] = 5
x := withData[1] // x is now 5

你可以從現有的切片取得一段元素範圍,建立新的切片。 同樣是使用方括號語法,但要同時指定起始索引(含)與結束索引(不含)。 如果沒有指定起始索引,預設為 0。 如果沒有指定結束索引,預設為切片的長度。

newSlice := withData[2:4]
// => []int{2,3}
newSlice := withData[:2]
// => []int{0,1}
newSlice := withData[2:]
// => []int{2,3,4,5}
newSlice := withData[:]
// => []int{0,1,2,3,4,5}

你可以使用append函式將元素加入切片。 下面我們把4和2加入a切片。

a := []int{1, 3}
a = append(a, 4, 2)
// => []int{1,3,4,2}

append總是會回傳一個新的切片;當我們只是想把元素加入現有的切片時,通常會像上面那樣,把它重新指定回我們當作第一個引數傳入的切片變數。

append也可以用來合併兩個切片:

nextSlice := []int{100,101,102}
newSlice  := append(withData, nextSlice...)
// => []int{0,1,2,3,4,5,100,101,102}

可變參數函式

一般來說,Go 的函式只接受固定數量的引數。 不過,在 Go 中也可以撰寫可變參數函式。

可變參數函式是指能接受數量不定之引數的函式。

如果函式定義中最後一個參數的型別前面加上省略符號...,那麼這個函式就能針對該參數接受任意數量的引數。

func find(a int, b ...int) {
    // ...
}

在上面的函式中,參數b是可變參數,我們可以傳入 0 個或多個引數給b。

find(5, 6)
find(5, 6, 7)
find(5)
Caution

可變參數必須是函式的最後一個參數。

可變參數函式的運作方式,是把數量不定的引數轉換成一個型別為該可變參數型別的切片。

以下是可變參數函式實作的範例。

func find(num int, nums ...int) {
    fmt.Printf("type of nums is %T\n", nums)

    for i, v := range nums {
        if v == num {
            fmt.Println(num, "found at index", i, "in", nums)
            return
        }
    }

    fmt.Println(num, "not found in ", nums)
}

func main() {
    find(89, 90, 91, 95)
    // =>
    // type of nums is []int
    // 89 not found in  [90 91 95]

    find(45, 56, 67, 45, 90, 109)
    // =>
    // type of nums is []int
    // 45 found at index 2 in [56 67 45 90 109]

    find(87)
    // =>
    // type of nums is []int
    // 87 not found in  []
}

在上面程式中的find(89, 90, 91, 95)這一行,傳給 find 函式的數量不定引數是90、91和95。 find函式預期num之後有一個可變的 int 參數。 因此,編譯器會把這三個引數轉換成型別為int的切片[]int{90, 91, 95},然後以nums傳給 find 函式。

有時候你已經有一個切片,想把它傳給可變參數函式。 這可以透過在切片後面加上...來達成。 這會告訴編譯器在可變參數函式中直接使用該切片。 在這種情況下,前面提到建立切片的步驟會直接省略。

list := []int{1, 2, 3}
find(1, list...) // "find" defined as shown above

說明

身為準魔術師,Elyse 需要練習一些基本功。她有一疊卡片,想要加以操作。

為了方便一些,她只用 1 到 10 的卡片。

1. 建立含有特定卡片的切片

練習卡片時,Elyse 喜歡從整副牌中最喜歡的三張開始:2、6 和 9。請寫一個函式FavoriteCards,回傳一個依序包含這三張卡片的切片。

cards := FavoriteCards()
fmt.Println(cards)
// Output: [2 6 9]

2. 從牌堆中取出一張卡片

從給定的牌堆中回傳位置index的卡片。

card := GetItem([]int{1, 2, 4, 1}, 2) // card == 4

如果索引超出範圍(也就是負數,或是超出牌堆末端),我們想回傳-1:

card := GetItem([]int{1, 2, 4, 1}, 10) // card == -1
Note

在 Go 的慣例中,會回傳錯誤,而不是回傳「超出範圍」的值。這裡當預期是正整數時,「超出範圍」的值是-1。回傳錯誤時,通常會一併回傳 zero value 與錯誤。如何回傳錯誤並附上正確的回傳值,會在之後的練習中介紹。

3. 替換牌堆中的一張卡片

將位置index的卡片替換成提供的新卡片,並回傳調整後的牌堆。請注意,這會修改傳入的切片,而這是預期的行為。

index := 2
newCard := 6
cards := SetItem([]int{1, 2, 4, 1}, index, newCard)
fmt.Println(cards)
// Output: [1 2 6 1]

如果索引超出範圍(也就是負數,或是超出牌堆末端),我們想把新卡片加到牌堆末端:

index := -1
newCard := 6
cards := SetItem([]int{1, 2, 4, 1}, index, newCard)
fmt.Println(cards)
// Output: [1 2 4 1 6]

4. 在牌堆頂端加入卡片

將value參數指定的卡片加到牌堆頂端。

slice := []int{3, 2, 6, 4, 8}
cards := PrependItems(slice, 5, 1)
fmt.Println(cards)
// Output: [5 1 3 2 6 4 8]

如果沒有給value參數任何引數,結果就會等於原始的切片。

slice := []int{3, 2, 6, 4, 8}
cards := PrependItems(slice)
fmt.Println(cards)
// Output: [3 2 6 4 8]

5. 從牌堆中移除一張卡片

從牌堆中移除位置index的卡片,並回傳牌堆。請注意,這可能會修改傳入的切片,這沒有問題。

cards := RemoveItem([]int{3, 2, 6, 4, 8}, 2)
fmt.Println(cards)
// Output: [3 2 4 8]

如果索引超出範圍(也就是負數,或是超出牌堆末端),我們想讓牌堆保持不變:

cards := RemoveItem([]int{3, 2, 6, 4, 8}, 11)
fmt.Println(cards)
// Output: [3 2 6 4 8]
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Go Exercism

準備好開始 紙牌魔術 了嗎?

註冊 Exercism,透過 34 個概念165 個練習 和真人引導來學習並精通 Go,全部免費。