Avatar ·

Как компактно создать двумерный срез (2D slice) в Go?

Всем привет! Я только начал изучать Go и столкнулся с проблемой при работе с двумерными срезами. Мне нужно создать матрицу, например, 3x4, чтобы хранить в ней целые числа. В других языках я просто пишу `int matrix[3][4]`, но в Go так не работает. Я пробовал сделать так: ```go matrix := make([][]int, 3) for i := range matrix { matrix[i] = make([]int, 4) } ``` Это работает, но код получается громоздким. Для простой инициализации приходится писать целый цикл. Есть ли более лаконичный способ? Может быть, есть однострочник или встроенная функция? Дополнительно: мне нужно, чтобы все элементы были заполнены нулями (или другим значением по умолчанию). И ещё — что делать, если я хочу создать "рваный" массив (jagged array), где строки разной длины? Например, первая строка — 3 элемента, вторая — 5, третья — 2. Заранее спасибо за помощь! Пример кода с моей текущей попыткой: ```go package main import "fmt" func main() { // Хочется как-то компактнее... rows, cols := 3, 4 matrix := make([][]int, rows) for i := range matrix { matrix[i] = make([]int, cols) } matrix[1][2] = 42 fmt.Println(matrix) } ``` <<>>

Почему нет встроенного однострочника?

В Go нет синтаксического сахара для двумерных срезов, потому что срез — это ссылочный тип, а make выделяет память только для одного уровня. Внутренние срезы нужно инициализировать отдельно. Однако есть несколько элегантных способов сделать код компактнее.

Способ 1: Функция-обёртка (рекомендуемый)

Самый чистый подход — написать небольшую вспомогательную функцию. Это переиспользуемый код, который легко читать:

func create2D(rows, cols int) [][]int {
    s := make([][]int, rows)
    for i := range s {
        s[i] = make([]int, cols)
    }
    return s
}

// Использование:
matrix := create2D(3, 4)
matrix[1][2] = 42

Способ 2: Однострочник с анонимной функцией

Если очень хочется в одну строку (например, для инициализации в тестах):

matrix := func(rows, cols int) [][]int {
    m := make([][]int, rows)
    for i := range m {
        m[i] = make([]int, cols)
    }
    return m
}(3, 4)

Способ 3: Оптимизация с одним выделением памяти

Для больших матриц (1000x1000 и больше) можно выделить один плоский массив, а затем разделить его на строки. Это уменьшает количество выделений памяти и улучшает производительность:

func create2DFlat(rows, cols int) [][]int {
    // Выделяем один непрерывный блок памяти
    data := make([]int, rows*cols)
    matrix := make([][]int, rows)
    for i := range matrix {
        matrix[i] = data[i*cols : (i+1)*cols]
    }
    return matrix
}

// Использование:
matrix := create2DFlat(3, 4)
matrix[1][2] = 42 // Работает точно так же

Способ 4: Заполнение ненулевыми значениями

Если нужно заполнить матрицу определённым значением (например, -1 или 1), используйте цикл после создания:

matrix := create2D(3, 4)
for i := range matrix {
    for j := range matrix[i] {
        matrix[i][j] = -1
    }
}

// Или более компактно с функцией:
func create2DVal(rows, cols, val int) [][]int {
    m := create2D(rows, cols)
    for i := range m {
        for j := range m[i] {
            m[i][j] = val
        }
    }
    return m
}

Рваные (jagged) массивы

Для создания среза с разной длиной строк просто укажите размер каждой строки отдельно:

jagged := make([][]int, 3)
jagged[0] = make([]int, 3) // 3 элемента
jagged[1] = make([]int, 5) // 5 элементов
jagged[2] = make([]int, 2) // 2 элемента

// Или с литералом:
jagged := [][]int{
    {1, 2, 3},
    {4, 5, 6, 7, 8},
    {9, 0},
}

Итоговая рекомендация

  • Для небольших матриц — используйте функцию create2D из Способа 1. Код читаемый и простой.
  • Для больших матриц — Способ 3 с одним выделением памяти. Быстрее и меньше мусора для GC.
  • Для рваных массивов — инициализируйте каждую строку отдельно или используйте литерал.

Запомните: в Go лаконичность часто достигается через маленькие вспомогательные функции, а не через встроенный синтаксис. Это нормально и даже поощряется сообществом.

<<>>

Альтернативный подход: одномерный срез с ручной индексацией

Если вам не нужна синтаксическая красота matrix[i][j], можно использовать плоский одномерный срез и вычислять индекс вручную. Это даёт максимальную производительность и компактность:

type Matrix struct {
    data []int
    cols int
}

func NewMatrix(rows, cols int) *Matrix {
    return &Matrix{
        data: make([]int, rows*cols),
        cols: cols,
    }
}

func (m *Matrix) Set(row, col, val int) {
    m.data[row*m.cols+col] = val
}

func (m *Matrix) Get(row, col int) int {
    return m.data[row*m.cols+col]
}

// Использование:
m := NewMatrix(3, 4)
m.Set(1, 2, 42)
fmt.Println(m.Get(1, 2)) // 42

Плюсы и минусы

  • Плюсы: одно выделение памяти, быстрый доступ (локальность кэша), легко сериализовать, легко передавать в C-код.
  • Минусы: нужно писать методы доступа, теряется синтаксический сахар [i][j].

Этот подход особенно полезен в библиотеках для научных вычислений или игр, где важна производительность. Для повседневных задач лучше использовать стандартные срезы с функцией-обёрткой.