Goでの配列とスライスの理解

紹介

Go言語では、配列スライスは、要素の順序付けられた序列を構成するデータ構造です。これらのデータ構造は、多くの関連した値を操作したい場合に大変便利です。それにより、属しているデータを一緒に保持し、コードを紧縮し、一度に多くの値に同じメソッドと操作を行えます。

Go言語の配列とスライスはどちらも要素の順序付けられた序列ですが、お互いに大きな違いがあります。Go言語の配列は、作成時に容量を定義した順序付けられた要素の序列です。配列が割り当てたサイズに達した後、サイズは変更できなくなります。しかし、スライスは、配列の変数長版であり、データ構造を使用する開発者にとってより柔軟性を提供します。スライスは、他の言語の配列と同様に考えられます。

これらの違いに基づいて、特定の状況ではそれぞれ使用することがあります。Go言語を新しく学び始めた場合、どちらを使用するか決めるのは混乱するかもしれません。スライスの多様性により多くの状況で適切な選択肢となりますが、配列を使用することは、プログラムの性能を最適化する特定の状況があるかもしれません。

この記事では、配列とスライスを詳細に説明し、これらのデータ型を選ぶ際に必要な情報を提供します。また、配列とスライスを宣言して操作する最も一般的な方法を复习します。チュートリアルは、まず配列の説明と操作方法を提供し、その後スライスとその違いを説明します。

配列

配列は、固定数の要素を持つ集合データ構造です。配列の大きさは静的であるため、データ構造は一度だけメモリを割り当てる必要があります。これは、変数長のデータ構造が将来より大きくなるか小さくなるために動的にメモリを割り当てる必要がある場合に比べて、メモリ割り当てを大幅に削減することができます。配列の固定長さは使い勝手が少し僵硬になる場合もありますが、一度のメモリ割り当てがプログラムのスピードと性能を向上させることができます。これにより、開発者は通常、データ構造が変数の量の要素を必要としない場合、プログラムを最適化する際に配列を使用します。

配列の定義

配列は、要素のデータ型を宣言した後に、[ ]の中で配列のサイズを宣言することで定義されます。Go言語の配列は、全ての要素が同じデータ型である必要があります。データ型の後ろに、要素の個別の値を宣言することができ、その際には{ }の中に書いてください。

以下は、配列を宣言する一般的なスキーマです:

[capacity]data_type{element_values}

注意:新たな配列の宣言は、独自の型を作成することになります。したがって、[2]int[3]intはどちらも整数要素を持っていますが、長さの違いによって、データ型が互換性がないことに注意してください。

配列の要素の値を宣言しない場合、デフォルトはゼロ値であり、これは配列の要素が空であることを意味します。整数の場合は0、文字列の場合は空の文字列を表します。

たとえば、以下のnumbers配列はまだ3つの整数要素の値を持っていません:

var numbers [3]int

numbersを印刷すると、以下の出力が得られます:

Output
[0 0 0]

要素の値を配列作成時に割り当てたい場合は、それらの値を{ }に入れます。文字列の配列に設定された値はこのように表示されます:

[4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}

変数に配列を格納し、出力することができます:

coral := [4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}
fmt.Println(coral)

前述の行でプログラムを実行すると、以下の出力が得られます:

Output
[blue coral staghorn coral pillar coral elkhorn coral]

配列の各要素間に区切りがないため、印刷された際にどの要素が終わっているか分かるのが困難になります。このため、fmt.Printf 関数を使うことが時々役立ちます。この関数は、要素を画面に印刷する前に文字列を書式化することができます。このコマンドに %q スイッチを与えることで、関数に要素に引号を付けるよう指示することができます。

fmt.Printf("%q\n", coral)

これにより、以下のようになります。

Output
["blue coral" "staghorn coral" "pillar coral" "elkhorn coral"]

今度は各アイテムには引用符がついています。\n スイッチは、書式設定機に行送りを追加するよう指示します。

配列を宣言する方法や、配列が構成されている内容を一般的に理解することができたら、今後、配列の要素をインデックス番号で指定する方法を学ぶことができます。

配列(及びスライス)のインデックス化

配列(スライスも同様)の各要素は、インデックス番号を使って個別に呼び出すことができます。各要素は、index number を 0 から始まり、カウントアップする int 値に対応しています。

以下の例では配列 coral を使用しますが、スライスを使用することもできます。なぜなら、両者においてインデックスングが同一であるからです。

配列 coral の場合、索引の構造は以下のようになります。

“blue coral” “staghorn coral” “pillar coral” “elkhorn coral”
0 1 2 3

最初の要素、String "blue coral"はIndex 0から始まり、Sliceの末尾はIndex 3で終わるElement "elkhorn coral"です。

SliceやArray内の各要素は索引番号を持つため、同様にsequential data typeとして manipulableです。

SliceまたはArrayの個別要素を呼び出すには、その索引番号を使います:

fmt.Println(coral[1])
Output
staghorn coral

この例では、Slice coralの索引番号は0-3であり、前述のテーブルより見えるように示されています。因みに、任意の要素を個別に呼び出すには、次のように索引番号を使用します:

coral[0] = "blue coral"
coral[1] = "staghorn coral"
coral[2] = "pillar coral"
coral[3] = "elkhorn coral"

SliceやArrayを索引する時、指数は常に正数であり、Goでは後方への索引(negative number)は許可されないため、エラーが発生します:

fmt.Println(coral[18])
Output
panic: runtime error: index out of range

ArrayまたはSliceを索引する時、必ず正数を使用します。Goでは後方への索引(negative number)を使うことはできません、そのためエラーが発生します:

fmt.Println(coral[-1])
Output
invalid array index -1 (index must be non-negative)

String要素をArrayまたはSlice内においてString要素と他のStringをconcatenateするには、+ Operatorを使用します:

fmt.Println("Sammy loves " + coral[0])
Output
Sammy loves blue coral

先前所述のように、Index number 0のString要素を “"Sammy loves "” とconcatenateしました。

Index numbersはArrayまたはSlice内の各Elementに対応しているため、それぞれのElementをdiscreteに取り出すことができます。これを示すため、次にElementを特定 Index でmodifyする方法を見てみます。

要素の修正

インデックスを使って配列やスライスの中の要素を変更することができます。 indicesに異なる値を設定することで、スライスや配列のデータにより詳細な制御を行うことができます。これにより、個別の要素をプログラム的に操作することができます。

配列coralのインデックス1の要素の文字列値を"staghorn coral"から"foliose coral"に変更したい場合、以下のように操作することができます。

coral[1] = "foliose coral"

以下のようにcoralを印刷すると、配列は変わっています。

fmt.Printf("%q\n", coral)
Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral"]

配列やスライスの個別の要素を操作する方法を学んだので、ここでコレクションデータ型を操作する際の柔軟性を高めるいくつかの関数について見てみましょう。

`len()`関数を使用して要素数をカウント

Go言語では、len()は配列やスライスを操作するための内蔵関数です。文字列と同様に、配列やスライスの長さをlen()を使用して計算することができます。

たとえば、coral配列に何个の要素があるかを探るには以下のようにします。

len(coral)

coral配列の長さを印刷した場合、以下の出力が受信されます:

Output
4

これはcoral配列の長さを4とするint型の値を正しく示しています。なぜなら、coral配列には4つのアイテムが含まれているからです。

coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

もし、より多くの要素を持つ整数型の配列を作成する場合、これにlen()関数を適用することができます:

numbers := [13]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
fmt.Println(len(numbers))

これは以下の出力になります:

Output
13

これらの例の配列は、比較的要素数が少ないが、len()関数は非常に大きな配列にどのような要素数があるかを決定する際に特に有用です。

次に、コレクションデータ型に要素を追加する方法を説明し、配列の固定長によって、これらの静的なデータ型に要素を追加するとエラーが発生することを示すでしょう。

要素の追加にappend()を使用する

append()は、Goの内蔵メソッドで、コレクションデータ型に要素を追加します。しかし、このメソッドは配列に使用できません。前述の通り、配列とスライスの主な違いは、配列のサイズを変更することができない点です。これ意味すると、配列の要素の値を変更することはできますが、定義後に配列をより大きくしたり小さくしたりすることはできません。

次に、coral配列を考えてみましょう:

coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

`"black coral"`をこの配列に追加したいと思っているとします。配列に`append()`関数を使用して、以下のように入力することを試みるとします。

coral = append(coral, "black coral")

これは、出力にエラーが発生し、以下のようになります。

Output
first argument to append must be slice; have [4]string

これを修正するためには、スライスデータ型についてもっと学びましょう。スライスを定義する方法、配列からスライスに変換する方法です。

スライス

スライスは、Golangでの変更可能な、または変更可能な順序付けの要素の集合のデータ型です。スライスのサイズは変動可能であるため、使用する際にはより柔軟性があります。将来的に扩展または缩小する必要があるデータコレクションに対して、スライスを使用することで、コレクションの長さを操作しようとする際にエラーが発生しないようにします。ほとんどの場合、この変更可能性は、配列と比較してスライスが必要とする可能性のあるメモリ再割り当てに対する利点があります。多くの要素を格納する必要がある場合や、要素を順次処理し、簡単に修正することができる場合、スライスデータ型を使用することが望まれます。

スライスの定義

スライスは、空のsquare brackets ([])で数据型を宣言し、curly brackets ({})内に要素を記述することで定義されます。与えている値は、数组ではなく、特定の長度を指定する必要がありません(例えば、intを含める必要がありません)。切片は、brackets内に何も入れず、そのような可変長さを表すことができます。

Sliceは、string型の要素を含むことができます:

seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}

切片をprintした時、切片内の要素が表示されます:

fmt.Printf("%q\n", seaCreatures)

これは次のような結果が出力されます:

Output
["shark" "cuttlefish" "squid" "mantis shrimp" "anemone"]

切片を作成したい場合、built-in make()関数を使用します:

oceans := make([]string, 3)

これで、切片を作成した後、printした際は以下のような結果が出力されます:

Output
["" "" ""]

切片を作成したい場合、third argumentをmake()関数に渡して、certain capacityを指定します:

oceans := make([]string, 3, 5)

これは、3個の元素を持つzeroed sliceを作成し、5個の要素をpre-allocateしたcapacityを持つsliceを作成します。

Sliceを知っていますが、これまではcoral配列を使うことはできませんでした。append()関数を使うには、先にarrayの一部をsliceしておく必要があります。

Slicing Arrays into Slices

配列の開始と終了地点をインデックス番号で決定して、配列の一部を呼び出すことができます。これはスライシングと呼ばれ、インデックス番号の範囲をコロンで区切った形式で[first_index:second_index]として作成して行います。しかし、配列をスライシングする際には、結果はスライスであり、配列ではないことに注意する必要があります。

例えば、coral配列の最初と最後の要素を取り除き、中間の要素だけを印刷したいときがあります。これはindex 1から始まり、index 3の直前まで終わるスライスを作成することで実現します。

fmt.Println(coral[1:3])

このようなプログラムを実行すると、以下のような結果が得られます。

Output
[foliose coral pillar coral]

スライスを作成する際、[1:3]のようにします。ここで最初の数字はスライスの開始地点(含む)であり、2番目の数字は最初の数字と取得したい要素の数の合計です。

array[starting_index : (starting_index + length_of_slice)]

この場合、2番目の要素(またはインデックス1)を開始地点とし、2つの要素を呼び出しました。計算は以下のようになります。

array[1 : (1 + 2)]

これがこの表示法に至る道です。

coral[1:3]

配列の開始地点または終了地点をスライスの開始地点または終了地点として設定したい場合、array[first_index:second_index]の構文の一つの数字を省略することができます。たとえば、coral配列の最初の3つの要素を印刷したい場合は、"blue coral", "foliose coral", "pillar coral"となります。これは以下のように入力することで実現します。

fmt.Println(coral[:3])

これは以下のような結果を印刷します。

Output
[blue coral foliose coral pillar coral]

このコードは配列の始めの部分を印刷し、index `3` の直前に止まりました。

配列の最後のすべてのアイテムを含めるには、構文を逆にする必要があります。

fmt.Println(coral[1:])

これにより、以下のスライスが得られます。

Output
[foliose coral pillar coral elkhorn coral]

この節では、配列の一部をスライシングを使用して呼び出す方法について話しました。次に、スライシングを使用して整个の配列をスライスに変換する方法を学びます。

配列からスライスへの変換

配列を作成し、変数長を持たせる必要がある場合、それをスライスに変換することができます。配列をスライスに変換するには、このチュートリアルの配列をスライスに分割するステップで学んだスライシングプロセスを使用しますが、ここでは終端を決定するインデックス番号を省略してスライス全体を選択します。

coral[:]

変数 `coral` をスライスに変換することはできません。なぜなら、Goで定義された変数の型は変更できないからです。これを回避するために、配列のすべての内容を新しい変数にスライスとしてコピーすることができます。

coralSlice := coral[:]

`coralSlice`を印刷すると、以下の出力が得られます。

Output
[blue coral foliose coral pillar coral elkhorn coral]

今、配列の節で見たように、新しく変換されたスライスに`black coral`要素を追加し、`append()`を使用してみてください。

coralSlice = append(coralSlice, "black coral")
fmt.Printf("%q\n", coralSlice)

これは追加された要素を含むスライスを出力します。

Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral"]

私たちは、一度のappend()ステートメントによって複数の要素を追加することもできます。

coralSlice = append(coralSlice, "antipathes", "leptopsammia")
Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia"]

2つのスライスを合体させるには、append()を使用することができますが、2番目の引数を追加する際には...展開スyntaxを使用する必要があります。

moreCoral := []string{"massive coral", "soft coral"}
coralSlice = append(coralSlice, moreCoral...)
Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

今度は、スライスに要素を追加する方法を学んだ後、削除する方法を見ていきましょう。

スライスから要素を削除する

他の言語とは異なり、Goはスライスから要素を削除するための内置の関数を提供していません。要素をスライスから削除するには、その要素をスライスし出す必要があります。

要素を削除するには、その要素の前の要素をスライスし出し、その要素の後の要素をスライスし出し、これらの2つの新しいスライスを結合して、削除したい要素を含まないようにする必要があります。

削除する必要のある要素のインデックスをiとします。そのようなプロセスのフォーマットは以下の通りです。

slice = append(slice[:i], slice[i+1:]...)

スライスcoralSliceから、"elkhorn coral"というアイテムを削除しましょう。このアイテムはインデックス位置3にあります。

coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}

coralSlice = append(coralSlice[:3], coralSlice[4:]...)

fmt.Printf("%q\n", coralSlice)
Output
["blue coral" "foliose coral" "pillar coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

今やcoralSliceスライスのインデックス位置3にある要素"elkhorn coral"は、もはや存在しません。

我们也可以使用相同的方法删除一个范围。比如说,我们不仅想要删除"elkhorn coral"这个项目,还想要删除"black coral""antipathes"。我们可以在表达式中使用一个范围来完成这个任务:

coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}

coralSlice = append(coralSlice[:3], coralSlice[6:]...)

fmt.Printf("%q\n", coralSlice)

这段代码将会从切片中移除索引345

Output
["blue coral" "foliose coral" "pillar coral" "leptopsammia" "massive coral" "soft coral"]

现在你已经知道如何向切片添加和删除元素,让我们来看看如何测量切片在任何给定时间可以容纳的数据量。

使用cap()测量切片的容量

由于切片的长度是可变的,len()方法并不是确定这种数据类型大小的最佳选项。相反,你可以使用cap()函数来了解切片的容量。这将显示切片可以容纳多少元素,这取决于为切片分配的内存量。

注意:因为数组的长度和容量总是相同的,所以cap()函数不会在数组上起作用。

使用cap()函数的常见用途之一是为切片的元素预设数量,然后程序化地填充这些元素。这避免了使用append()添加元素可能导致的潜在不必要的分配。

まず、数字のリストを作成したいときのシナリオを見てみましょう。数字を0から3まで追加します。ループ内でappend()を使用してこれを行うことができますが、まずスライスを事前に割り当てることができます。そしてcap()を使用してループを回して値を埋めます。

まず、append()の使用を見てみましょう。

numbers := []int{}
for i := 0; i < 4; i++ {
	numbers = append(numbers, i)
}
fmt.Println(numbers)
Output
[0 1 2 3]

この例では、スライスを作成し、forループを作成しました。このループ変数iの現在の値をnumbersスライスのインデックスに追加します。しかし、これはプログラムを遅くする不必要なメモリ割り当てを引き起こす可能性があります。空のスライスに追加するたびに、append()の呼び出しにより、プログラムがスライスの容量を確認します。追加される要素がスライスの容量を超えると、プログラムは追加のメモリを割り当てる必要があります。これはプログラムに追加のオーバーヘッドを引き起こし、実行速度を遅くすることができます。

次に、append()を使用せずにスライスを作成する方法を見てみましょう。

numbers := make([]int, 4)
for i := 0; i < cap(numbers); i++ {
	numbers[i] = i
}

fmt.Println(numbers)

Output
[0 1 2 3]

この例では、make()を使用してスライスを作成し、4の要素を事前に割り当てました。そして、ループ内でcap()関数を使用して、それぞれのゼロ化された要素を回していき、割り当てられた容量に達するまで各要素に値を入力します。それぞれのループで、iループ変数の現在の値をnumbersスライスのインデックスに配置します。

append()cap()戦略は機能的に等価ですが、cap()の例はappend()関数を使用した場合に必要な追加のメモリ割り当てを避けることができます。

multidimensional slicesの構築

他のスライスを要素とするスライスを定義することもできます。それぞれの括弧リストは親スライスのより大きな括弧内に含まれます。このようなスライスのコレクションは multidimensional slicesと呼ばれます。これらは二次元座標を表すことが考えられます。たとえば、それぞれ6要素の長さの5つのスライスが2次元のグリッドを表しています。

次の multidimensional sliceを見てみましょう。

seaNames := [][]string{{"shark", "octopus", "squid", "mantis shrimp"}, {"Sammy", "Jesse", "Drew", "Jamie"}}

このスライスの中の要素にアクセスするには、構造の各次元に対して索引を使用する必要があります。

fmt.Println(seaNames[1][0])
fmt.Println(seaNames[0][0])

前述のコードでは、まず索引01番目のスライス内の要素を識別し、それから索引00番目のスライス内の要素を示します。これにより以下の結果が得られます。

Output
Sammy shark

残りの各要素の索引値は以下の通りです。

seaNames[0][0] = "shark"
seaNames[0][1] = "octopus"
seaNames[0][2] = "squid"
seaNames[0][3] = "mantis shrimp"

seaNames[1][0] = "Sammy"
seaNames[1][1] = "Jesse"
seaNames[1][2] = "Drew"
seaNames[1][3] = "Jamie"

multidimensional sliceについて作業する際に、特定の内部嵌套されたsliceの中の要素にアクセスするために、1つ以上のインデックス番号を参照する必要があることを忘れずにすることが重要です。

結論

このチュートリアルで、あなたはGoでの配列とsliceの基礎操作を学びました。あなたは配列が固定長であり、sliceが変動長であることを示すいくつかの练习を経験し、この違いがデータ構造の使用状況にどのように影響するかを発見しました。

Goでのデータ構造をさらに学ぶには、私たちのGoでのマップ理解の記事をチェックしてくださいか、Goでコードを書くシリーズを全体的に探りましょう。

Source:
https://www.digitalocean.com/community/tutorials/understanding-arrays-and-slices-in-go