Go 言語における多次元配列

GolangBeginner
オンラインで実践に進む

はじめに

前のセクションでは、配列の基本的な使い方について学びました。

配列にはどのような種類の要素が含まれることができますか?

Go 言語では、配列の要素は整数、文字列、またはカスタム型など、任意の基本型であることができます。

配列の要素が配列である場合、どうなりますか?

その場合、多次元配列ができます:

多次元配列

上の図に示すように、紫色のボックスが元の配列です。

紫色のボックスの配列の各要素は新しい配列(赤いボックス)です。

キーコンセプト

  • 二次元配列の定義
  • 二次元配列の初期化
  • 二次元配列の反復処理
  • 多次元配列の使用

二次元配列の定義

一般的な定義

通常の配列を定義する方法を覚えていますか?

var variableName [elementCount]variableType

では、この最も単純な方法を使って二次元配列をどのように定義するのでしょうか?

var variableName [elementCount][elementCount]variableType

違いは、元の[elementCount]の前にもう 1 つ[elementCount]を追加して、[elementCount][elementCount]の形式になっていることだけです。

たとえば、型intの容量が10*10の二次元配列aを定義したい場合、次の構文を使うことができます。

var a [10][10]int

短い定義

短い変数宣言と同じように、配列、さらには多次元配列を宣言するための短い定義方法を使うことができます。

:=を使って配列、多次元配列を宣言することができます。

たとえば:

a := [10][10]int{}

これにより、サイズ10*10の整数の二次元配列が定義されます。

二次元配列の初期化

一次元配列と同様に、二次元配列を次のような方法で初期化することができます。

  • 初期化リスト
  • 推論される長さによる初期化
  • 指定されたインデックス値による初期化

これらの方法をどのように使うか覚えていますか?

前のセクションでは、一次元配列でこれらの方法をどのように使うか学びました。忘れてしまっても大丈夫です。

次のセクションでは、これらの 3 つの初期化方法を見直し、二次元配列に拡張します。

初期化リストを使用した二次元配列の初期化

前のセクションで作成したarray.goファイルを引き続き使いましょう。作業を保存していない場合は、次のようにファイルを作成できます。

touch ~/project/array.go

array.goに次のコードを記述します。

package main

import "fmt"

func main() {
    // 自動的に 0 で初期化される
    var simpleArray [3][3]int
    // 指定された初期値を使って初期化し、欠けている要素にはデフォルト値を使う
    var numArray = [3][3]int{{1, 2, 3}, {2, 3, 4}}
    // 指定された初期値を使って初期化する
    var cityArray = [2][2]string{{"London", "Chengdu"}, {"Paris", "Boston"}}
    fmt.Println(simpleArray) // [[0 0 0] [0 0 0] [0 0 0]]
    fmt.Println(numArray)    // [[1 2 3] [2 3 4] [0 0 0]]
    fmt.Println(cityArray)   // [[London Chengdu] [Paris Boston]]
}

上記のコードは、初期化リストを使って二次元配列を初期化する 3 つの方法を示しています。

次のコマンドを使ってコードを実行します。

go run ~/project/array.go

出力は次のようになります。

[[0 0 0] [0 0 0] [0 0 0]]
[[1 2 3] [2 3 4] [0 0 0]]
[[London Chengdu] [Paris Boston]]

値を変更して、一次元配列の初期化方法を見直すことができます。

推論された長さで二次元配列を初期化する

二次元配列でも、一次元配列と同じように、推論される長さの方法を使って初期化することができます。

array.goに次のコードを記述します。

package main

import "fmt"

func main() {
    // 自動的に 0 で初期化される
    var simpleArray [3][3]int
    // 指定された初期値を使って初期化し、欠けている要素にはデフォルト値を使う
    var numArray = [...][]int{{1, 2, 3, 3}, {2, 3, 4, 3}, {0}}
    // 指定された初期値を使って初期化する
    var cityArray = [...][2]string{{"London", "Chengdu"}, {"Paris", "Boston"}}
    fmt.Println(simpleArray) // [[0 0 0] [0 0 0] [0 0 0]]
    fmt.Println(numArray)    // [[1 2 3 3] [2 3 4 3] [0]]
    fmt.Println(cityArray)   // [[London Chengdu] [Paris Boston]]
}

上記のコードは、推論される長さを使って二次元配列を初期化する方法を示しています。

go run ~/project/array.go

出力は初期化リストの方法と同じです。

[[0 0 0] [0 0 0] [0 0 0]]
[[1 2 3 3] [2 3 4 3] [0]]
[[London Chengdu] [Paris Boston]]

ただし、一次元配列とは異なり、二次元配列の推論される長さの初期化では、... 記号は 最初の 角括弧の中にのみ存在することができます。

たとえば:

var numArray = [...][]int{{1, 2, 3, 3}, {2, 3, 4, 3}}

このコードは正しいですが、次の 2 つのバリエーションは間違っています。

var numArray = [][...]int{{1, 2, 3, 3}, {2, 3, 4, 3}}
var numArray = [...][...]int{{1, 2, 3, 3}, {2, 3, 4, 3}}

また、numArraycityArray を比較してみましょう。

cityArray では、二次元配列のサイズの 2 番目のパラメータ を指定していることがわかります。次のようになっています。

var cityArray = [...][2]string{{"London", "Chengdu"}, {"Paris", "Boston"}}

これは、初期化時に各サブ配列の サイズ2 に指定していることを意味します。

初期化時に与えられた値が足りない場合、欠けている要素はデータ型のデフォルト値で埋められます。

与えられた値の数が指定されたサイズを超える場合、エラーが発生します。

指定されたインデックス値で二次元配列を初期化する

一次元配列と同様に、二次元配列でも指定されたインデックス値を使って初期化することができます。そのプロセスは似ています。

array.goに次のコードを記述します。

package main

import "fmt"

func main() {
    a := [...][]int{1: {1, 2, 3}, 3: {4, 7, 9}}
    fmt.Println(a)                  // [[] [1 2 3] [] [4 7 9]]
    fmt.Printf("Type of array a: %T\n", a) // Type of array a: [4][]int
}
go run ~/project/array.go

出力は次のようになります。

[[] [1 2 3] [] [4 7 9]]
Type of array a: [4][]int

上記のコードは、長さが不定の二次元配列として配列aを定義しています。インデックス1の配列に値[1 2 3]を割り当て、インデックス3の配列に値[4 7 9]を割り当てています。

自動的に推論される長さの場合、配列aの型は[4][]intになります。

二次元配列の反復処理

二次元配列をどのように反復処理するか。

一次元配列を学んだとき、配列を反復処理するために 2 つの方法を使いました。rangeキーワードを使う方法とインデックス番号を使う方法です。

今度はこれら 2 つの方法を使って二次元配列を反復処理してみましょう。

array.goに次のコードを記述します。

package main

import "fmt"

func main() {
    a := [...][]int{{123, 321, 222}, {404, 501, 503}, {857, 419, 857}}
    // 方法 1:range キーワードを使う
    fmt.Println("Traversing the Two-Dimensional Array Using the range Keyword")
    for index, value := range a {
        for i, j := range value {
            fmt.Println(index, i, j)
        }
    }
    // 方法 2:インデックス番号を使う
    fmt.Println("\nTraversing the Two-Dimensional Array Using Index Numbers")
    for i := 0; i < len(a); i++ {
        for j := 0; j < len(a[i]); j++ {
            fmt.Println(i, j, a[i][j])
        }
    }
}

上記のコードは、二次元配列を反復処理する 2 つの方法を示しています。

go run ~/project/array.go

出力は次のようになります。

Traversing the Two-Dimensional Array Using the range Keyword
0 0 123
0 1 321
0 2 222
1 0 404
1 1 501
1 2 503
2 0 857
2 1 419
2 2 857

Traversing the Two-Dimensional Array Using Index Numbers
0 0 123
0 1 321
0 2 222
1 0 404
1 1 501
1 2 503
2 0 857
2 1 419
2 2 857

両方の方法で同じ結果が得られますが、根本的には異なっています。これらの違いは、実際の配列の使い方で特に明らかになります。

配列の実用的な用途

前のセクションでは、配列の反復処理の 2 つの方法が根本的に異なることを述べました。

これを小さな例で説明しましょう。

array.goに次のコードを記述します。

package main

import "fmt"

func main() {
    a := [...][]int{{123, 321, 222}, {404, 501, 503}, {857, 419, 857}}
    // 方法 1:range キーワードを使う
    fmt.Println("Traversing the Two-Dimensional Array Using the range Keyword")
    for _, value := range a {
        for _, j := range value {
            fmt.Println(j)
        }
    }
    fmt.Println(a)
    // 方法 2:インデックス番号を使う
    fmt.Println("\nTraversing the Two-Dimensional Array Using Index Numbers")
    for i := 0; i < len(a); i++ {
        for j := 0; j < len(a[i]); j++ {
            fmt.Println(a[i][j])
            a[i][j] = 0
        }
    }
    fmt.Println(a)
}

上記のコードは、二次元配列のすべての値を0に設定するための 2 つの異なる方法を示しています。

go run ~/project/array.go

プログラムの出力は次のようになります。

Traversing the Two-Dimensional Array Using the range Keyword
123
321
222
404
501
503
857
419
857
[[123 321 222] [404 501 503] [857 419 857]]

Traversing the Two-Dimensional Array Using Index Numbers
123
321
222
404
501
503
857
419
857
[[0 0 0] [0 0 0] [0 0 0]]

range キーワードを使って配列を反復処理する場合、その値を変更しても影響がないことがわかります。一方、インデックス番号を使って配列を反復処理した後、その値を変更すると有効になります。これは、range キーワードを使って配列を反復処理する場合、ループ変数jは実際には配列の値のコピーであるためです。コピーの値を変更しても元の配列aには影響しません。ただし、インデックス番号を使って配列を反復処理する場合、元の配列の値を変更することは有効です。

二次元配列の拡大

時には、三次元配列や四次元配列を使う必要があります。

一次元配列から二次元配列に拡張するのと同じように、高次元配列に拡張することもできます。

簡単な例を見てみましょう。

package main

import "fmt"

func main() {
    a := [2][2][2]int{}
    for i := 0; i < 2; i++ {
        for j := 0; j < 2; j++ {
            for k := 0; k < 2; k++ {
                a[i][j][k] = 1
            }
        }
    }
    fmt.Println(a)
}

上記のコードは、三次元配列を定義して使用する方法を示しています。四次元以上の配列についても同じプロセスです。

go run ~/project/array.go

出力は次のようになります。

[[[1 1] [1 1]] [[1 1] [1 1]]]

ただし、高次元配列は一般的にはあまり使われません。だから、概念を理解するだけで十分です。

まとめ

この実験では、以下のことを学びました。

  • 二次元配列を定義する 2 つの方法
  • 二次元配列を初期化する 3 つの方法
  • 二次元配列を反復処理する 2 つの方法
  • 実際の使用での 2 つの反復処理方法の違い
  • 多次元配列の使用の概要