Как компактно создать двумерный срез (2D slice) в Go?
Почему нет встроенного однострочника?
В 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].
Этот подход особенно полезен в библиотеках для научных вычислений или игр, где важна производительность. Для повседневных задач лучше использовать стандартные срезы с функцией-обёрткой.