Go言語多次元配列

Go言語配列

多次元配列は配列の配列であり、それは1つのデータテーブル或行列として考えることができ、その各要素は複数のインデックスでアクセスできます。

Go言語の多次元配列は、表形式データ、行列演算、ゲームボードなどの構造化された情報を処理するために使用できます。

基本概念

  • 1次元配列:直線上の点のようなもので、1つの座標(インデックス)だけで特定の要素を見つけることができます
  • 2次元配列:テーブルのようなもので、行と列の2つの座標が必要です
  • 3次元配列:立方体のようなもので、縦・横・高さの3つの座標が必要です
  • より高次元:理論的には多次元にできますが、実際のプログラミングでは2次元と3次元が最もよく使われます

身近な駐車スペースに例えることができます:

  • 1次元配列= 1列の駐車スペース(駐車番号だけが必要)
  • 2次元配列= 多層駐車場(フロアと駐車番号が必要)
  • 3次元配列= 複数の多層駐車場(駐車場番号、フロア、駐車番号が必要)

Go言語は多次元配列をサポートしています。以下は一般的な多次元配列の宣言方法です:

var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type

宣言と初期化:

// 2次元配列を宣言 var 数组名 [行数][列数]元素类型 // 宣言と初期化 var 数组名 [行数][列数]元素类型 = [行数][列数]元素类型{初始化值}

以下の例では、3次元の整数配列を宣言しています:

var threedim [5][10][4]int

2次元配列

2次元配列は最も単純な多次元配列です。2次元配列は本質的に1次元配列から構成されます。2次元配列の定義方法は次のとおりです:

var arrayName [ x ][ y ] variable_type

variable_type は Go言語のデータ型、arrayName は配列名です。2次元配列はテーブルと考えることができ、x は行、y は列です。下図は、3行4列の2次元配列 a を示しています:

2次元配列の要素は、a[ i ][ j ]によりアクセスできます。

例

package main

import "fmt"

func main() {
    // Step 1: 配列を作成
    values := [][]int{}

    // Step 2: append() 関数を使用して、空の2次元配列に1次元配列を2行追加
    row1 := []int{1, 2, 3}
    row2 := []int{4, 5, 6}
    values = append(values, row1)
    values = append(values, row2)

    // Step 3: 2行のデータを表示
    fmt.Println("Row 1")
    fmt.Println(values[0])
    fmt.Println("Row 2")
    fmt.Println(values[1])

    // Step 4: 最初の要素にアクセス
    fmt.Println("最初の要素は:")
    fmt.Println(values[0][0])
}

上記の例の実行出力結果は次のとおりです:

Row 1
[1 2 3]
Row 2
[4 5 6]
第一个元素为:
1

2次元配列の初期化

多次元配列は波括弧を使用して初期化できます。以下の例は、3行4列の2次元配列です:

a := [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11},   /* 第三行索引为 2 */
}
注意:上記のコードの最後から2行目の}にはカンマが必要です。なぜなら最後の行の}を単独の行にすることはできません。次のように書くこともできます:
a := [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11}}   /* 第三行索引为 2 */

以下の例では、2行2列の2次元配列を初期化します:

例

package main

import "fmt"

func main() {
    // 2次元配列を作成
    sites := [2][2]string{}

    // 2次元配列に要素を追加
    sites[0][0] = "Google"
    sites[0][1] = "サンプル"
    sites[1][0] = "Taobao"
    sites[1][1] = "Weibo"

    // 結果を表示
    fmt.Println(sites)
}

上記の例の実行出力結果は次のとおりです:

[[Google サンプル] [Taobao Weibo]]

2次元配列へのアクセス

2次元配列は座標を指定してアクセスします。配列の行インデックスと列インデックスのように、例えば:

val := a[2][3]
或
var value int = a[2][3]

上記の例では、2次元配列 val の3行目の4番目の要素にアクセスしています。

2次元配列は、入れ子ループを使用して要素を出力できます:

例

package main

import "fmt"

func main() {
   /* 配列 - 5行2列 */
   var a = [5][2]int{ {0,0}, {1,2}, {2,4}, {3,6},{4,8}}
   var i, j int

   /* 配列要素を出力 */
   for  i = 0; i < 5; i++ {
      for j = 0; j < 2; j++ {
         fmt.Printf("a[%d][%d] = %d\n", i,j, a[i][j] )
      }
   }
}

上記の例の実行出力結果は次のとおりです:

a[0][0] = 0
a[0][1] = 0
a[1][0] = 1
a[1][1] = 2
a[2][0] = 2
a[2][1] = 4
a[3][0] = 3
a[3][1] = 6
a[4][0] = 4
a[4][1] = 8

以下の例では、各次元の要素数が異なる多次元配列を作成します:

例

package main

import "fmt"

func main() {
    // 空の2次元配列を作成
    animals := [][]string{}

    // 3つの1次元配列を作成、各配列の長さは異なる
    row1 := []string{"fish", "shark", "eel"}
    row2 := []string{"bird"}
    row3 := []string{"lizard", "salamander"}

    // append() 関数を使用して1次元配列を2次元配列に追加
    animals = append(animals, row1)
    animals = append(animals, row2)
    animals = append(animals, row3)

    // ループで出力
    for i := range animals {
        fmt.Printf("Row: %v\n", i)
        fmt.Println(animals[i])
    }
}

上記の例の実行出力結果は次のとおりです:

Row: 0
[fish shark eel]
Row: 1
[bird]
Row: 2
[lizard salamander]

配列要素のアクセスと変更

要素へのアクセス

例

package main

import "fmt"

func main() {
    // 3x3のマトリックスを作成
    matrix := [3][3]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }
   
    // 単一の要素にアクセス
    fmt.Println("1行目2列目:", matrix[0][1])  // 出力: 2
    fmt.Println("3行目3列目:", matrix[2][2]) // 出力: 9
   
    // 行全体にアクセス
    fmt.Println("2行目:", matrix[1])  // 出力: [4 5 6]
   
    // すべての要素を走査
    fmt.Println("\nすべての要素を走査:)
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            fmt.Printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j])
        }
    }
}

要素の変更

例

package main

import "fmt"

func main() {
    // 2x2のゼロ値マトリックスを作成
    var grid [2][2]int
   
    fmt.Println("変更前のマトリックス:", grid)
   
    // 特定の位置の要素を変更
    grid[0][0] = 10
    grid[0][1] = 20
    grid[1][0] = 30
    grid[1][1] = 40
   
    fmt.Println("変更後のマトリックス:", grid)
   
    // 1行を一括変更
    grid[0] = [2]int{100, 200}
    fmt.Println("1行目を変更後:", grid)
}

3次元およびそれ以上の多次元配列

3次元配列の例

例

package main

import "fmt"

func main() {
    // 2x3x4の3次元配列を宣言
    // 2つの平面があり、各平面が3行4列と考えることができます
    var cube [2][3][4]int
   
    // 3次元配列を初期化
    cube = [2][3][4]int{
        { // 最初の平面
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12},
        },
        { // 2番目の平面
            {13, 14, 15, 16},
            {17, 18, 19, 20},
            {21, 22, 23, 24},
        },
    }
   
    // 3次元配列の要素にアクセス
    fmt.Println("cube[0][1][2] =", cube[0][1][2])  // 出力: 7
    fmt.Println("cube[1][2][3] =", cube[1][2][3])  // 出力: 24
   
    // 3次元配列を走査
    fmt.Println("\n3次元配列の内容:")
    for i := 0; i < 2; i++ {
        fmt.Printf("平面 %d:\n", i)
        for j := 0; j < 3; j++ {
            for k := 0; k < 4; k++ {
                fmt.Printf("%3d ", cube[i][j][k])
            }
            fmt.Println()
        }
        fmt.Println()
    }
}

下図を参照してください:


多次元配列の一般的な操作

1. rangeを使用した走査

例

package main

import "fmt"

func main() {
    // 2次元配列を作成
    scores := [3][4]int{
        {85, 90, 78, 92},
        {88, 76, 95, 89},
        {92, 85, 88, 90},
    }
   
    fmt.Println("学生成績表:")
   
    // rangeを使用して2次元配列を走査
    for i, row := range scores {
        fmt.Printf("学生 %d の成績: ", i+1)
        for j, score := range row {
            fmt.Printf("%d ", score)
            // インデックスと値の両方を使用する場合
            _ = j // 未使用変数の警告を回避
        }
        fmt.Println()
    }
   
    // インデックスを気にせず、値だけを気にする
    total := 0
    count := 0
    for _, row := range scores {
        for _, score := range row {
            total += score
            count++
        }
    }
    fmt.Printf("\n平均点: %.2f\n", float64(total)/float64(count))
}

2. 配列の長さの取得

例

package main

import "fmt"

func main() {
    // 不規則な多次元配列を作成
    jagged := [3][3]int{
        {1, 2, 3},
        {4, 5},
        {6, 7, 8, 9}, // 注意:ここでは各行の長さが一致している必要があるため、コンパイルエラーになります
    }
   
    // 正しい例:配列の次元を取得
    matrix := [4][5]int{}
   
    // 行数を取得
    rows := len(matrix)
    fmt.Println("行数:", rows)  // 出力: 4
   
    // 最初の行の列数を取得(すべての行の長さは同じ)
    cols := len(matrix[0])
    fmt.Println("列数:", cols)  // 出力: 5
   
    // 総要素数を取得
    totalElements := rows * cols
    fmt.Println("総要素数:", totalElements)  // 出力: 20
}

3. 配列の比較

例

package main

import "fmt"

func main() {
    // 同一の2次元配列を2つ作成
    a := [2][2]int{{1, 2}, {3, 4}}
    b := [2][2]int{{1, 2}, {3, 4}}
    c := [2][2]int{{1, 2}, {3, 5}}
   
    // 配列は直接比較できます(次元が完全に同じ場合のみ)
    fmt.Println("a == b:", a == b)  // 出力: true
    fmt.Println("a == c:", a == c)  // 出力: false
   
    // 注意:異なる次元の配列は比較できない
    // d := [2][3]int{{1, 2, 3}, {4, 5, 6}}
    // fmt.Println(a == d) // コンパイルエラー:型が一致しない
}

実際の応用シーン

シーン1:ゲームボード(三目並べ)

例

package main

import "fmt"

func main() {
    // 3x3の三目並べボードを初期化
    var board [3][3]string
   
    // 空に初期化
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            board[i][j] = " "
        }
    }
   
    // ゲームをシミュレーション
    board[0][0] = "X"
    board[1][1] = "O"
    board[2][2] = "X"
   
    // ボードを出力
    fmt.Println("三目並べボード:")
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            fmt.Printf(" %s ", board[i][j])
            if j < 2 {
                fmt.Printf("|")
            }
        }
        fmt.Println()
        if i < 2 {
            fmt.Println("---+---+---")
        }
    }
}

シーン2:学生成績管理システム

例

package main

import "fmt"

func main() {
    // 定義:3人の学生、各学生に4つのコース
    var grades [3][4]float64
   
    // 学生の成績を入力
    grades = [3][4]float64{
        {85.5, 90.0, 78.5, 92.0},  // 学生1の成績
        {88.0, 76.5, 95.0, 89.5},  // 学生2の成績
        {92.5, 85.0, 88.5, 90.0},  // 学生3の成績
    }
   
    // 各学生の平均点を計算
    fmt.Println("学生の成績統計:")
    for i, studentGrades := range grades {
        sum := 0.0
        for _, grade := range studentGrades {
            sum += grade
        }
        average := sum / float64(len(studentGrades))
        fmt.Printf("学生 %d: 平均点 = %.2f\n", i+1, average)
    }
   
    // 各コースの平均点を計算
    fmt.Println("\nコース平均点:")
    for j := 0; j < 4; j++ {
        sum := 0.0
        for i := 0; i < 3; i++ {
            sum += grades[i][j]
        }
        average := sum / 3.0
        fmt.Printf("コース %d: 平均点 = %.2f\n", j+1, average)
    }
}

シーン3:画像ピクセル処理

例

package main

import "fmt"

func main() {
    // シンプルな3x3グレースケール画像をシミュレーション
    // 各ピクセル値の範囲:0(黒) ~ 255(白)
    var image [3][3]int
   
    // 画像を初期化(シンプルなグラデーション)
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            image[i][j] = (i + j) * 50
        }
    }
   
    // 元の画像を表示
    fmt.Println("元の画像:")
    displayImage(image)
   
    // 画像処理:輝度を上げる
    fmt.Println("\n輝度を上げた後の画像:")
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            // 輝度を上げるが、255を超えない
            newValue := image[i][j] + 50
            if newValue > 255 {
                newValue = 255
            }
            image[i][j] = newValue
        }
    }
    displayImage(image)
}

func displayImage(img [3][3]int) {
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            fmt.Printf("%3d ", img[i][j])
        }
        fmt.Println()
    }
}

注意事項とベストプラクティス

1. 配列の長さは型の一部である

例

package main

import "fmt"

func main() {
    // これらは異なる型です!
    var a [2][3]int
    var b [3][2]int
   
    // 以下のコードはコンパイルエラーになる
    // a = b // エラー:型が一致しない
   
    fmt.Printf("a の型: %T\n", a)  // [2][3]int
    fmt.Printf("b の型: %T\n", b)  // [3][2]int
}

2. 値型 vs 参照型

例

package main

import "fmt"

func main() {
    // 配列は値型です
    original := [2][2]int{{1, 2}, {3, 4}}
   
    // 代入するとコピーが作成されます
    copy := original
   
    // コピーを変更しても元の配列には影響しません
    copy[0][0] = 100
   
    fmt.Println("元の配列:", original)  // [[1 2] [3 4]]
    fmt.Println("コピーの配列:", copy)      // [[100 2] [3 4]]
   
    // 参照セマンティクスが必要な場合は、スライスを使用できます(後続の記事で紹介)
}

3. パフォーマンスの考慮

  • メモリ連続:多次元配列はメモリ内で連続して格納されるため、アクセス速度が速い
  • 固定サイズ:配列の長さはコンパイル時に決定され、動的に変更できない
  • 適したシナリオ:データサイズが既知で固定されている場合、配列が最適な選択です

よくある質問(FAQ)

Q1: 多次元配列とネストされたスライスの違いは何ですか?

特性 多次元配列 ネストされたスライス
サイズ 固定、コンパイル時に決定 動的、実行時に変更可能
メモリ 連続割り当て 非連続の可能性
パフォーマンス アクセス速度が速い やや遅い、追加オーバーヘッドあり
使用シナリオ データサイズが既知 データサイズが可変

Q2: 不規則な2次元配列を作成するには?

Goの配列は各行の長さが同じである必要があります。不規則な構造が必要な場合は、スライスを使用する必要があります:

例

// スライスを使用して不規則な構造を作成
irregular := [][]int{
    {1, 2, 3},
    {4, 5},          // この行は2つの要素のみ
    {6, 7, 8, 9},    // この行は4つの要素
}

Q3: 多次元配列を関数の引数として使用できますか?

はい、ただし配列は値型であり、大きな配列を渡すとパフォーマンスのオーバーヘッドが発生することに注意してください:

例

func processMatrix(matrix [3][3]int) [3][3]int {
    // 行列を処理...
    matrix[0][0] = 100
    return matrix
}

// より良い方法はポインタまたはスライスを使用することです
func processMatrixPtr(matrix *[3][3]int) {
    matrix[0][0] = 100
}

Go言語の配列

その他の拡張