トラック
/
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関数を使うと、スライスに要素を追加できます。 次の例では、aスライスに4と2を追加しています。

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

appendは常に新しいスライスを返します。既存のスライスに要素を追加したいだけのときは、上の例のように、最初の引数として渡したスライス変数に代入し直すのが一般的です。

appendを使って2つのスライスを結合することもできます。

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が可変長で、bには0個以上の引数を渡せます。

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の仮引数を期待します。 そのため、これら3つの引数はコンパイラーによって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はデッキのお気に入りの3枚、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を学んでマスターできます。すべて無料です。