Что представляет собой сжатый способ создания 2D-фрагмента в Go?
Я учусь, пройдя Tour of Go. В одном из упражнений мне предлагается создать 2D-фрагмент строк dy
и столбцов dx
, содержащих uint8
. Мой текущий подход, который работает, заключается в следующем:
a:= make([][]uint8, dy) // initialize a slice of dy slices
for i:=0;i<dy;i++ {
a[i] = make([]uint8, dx) // initialize a slice of dx unit8 in each of dy slices
}
Я думаю, что итерация через каждый срез для его инициализации слишком многословна. И если бы срез имел больше размеров, код стал бы громоздким. Есть ли сжатый способ инициализации 2D (или n-мерных) срезов в Go?
Ответы
Ответ 1
Нет более сжатого способа, что вы сделали, это "правильный" способ; потому что срезы всегда одномерные, но могут быть составлены для построения объектов с большими размерами. См. Этот вопрос для получения дополнительной информации: Go: Как представление памяти двухмерного массива.
Одна вещь, которую вы можете упростить, заключается в использовании конструкции for range
:
a := make([][]uint8, dy)
for i := range a {
a[i] = make([]uint8, dx)
}
Также обратите внимание, что если вы инициализируете свой фрагмент с помощью составного литерала, вы получите это для "бесплатного", например:
a := [][]uint8{
{0, 1, 2, 3},
{4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]
Да, у этого есть свои пределы, поскольку, кажется, вам нужно перечислить все элементы; но есть некоторые трюки, а именно, вам не нужно перечислять все значения, только те, которые не являются нулевыми значениями типа элемента срез. Подробнее об этом см. Ключи в инициализации массива golang.
Например, если вы хотите срез, где первые 10 элементов являются нулями, а затем следует за 1
и 2
, его можно создать следующим образом:
b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]
Также обратите внимание, что если вы используете массивы вместо slices, его можно создать очень легко:
c := [5][5]uint8{}
fmt.Println(c)
Выход:
[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
В случае массивов вам не нужно перебирать "внешний" массив и инициализировать "внутренние" массивы, поскольку массивы не являются дескрипторами, а значениями. См. Сообщение в блоге Массивы, срезы (и строки): Механика "append" для более подробной информации.
Попробуйте примеры на Go Playground.
Ответ 2
Есть два способа использования срезов для создания матрицы. Давайте посмотрим на различия между ними.
Первый метод:
matrix := make([][]int, n)
for i := 0; i < n; i++ {
matrix[i] = make([]int, m)
}
Второй метод:
matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
matrix[i] = rows[i*m : (i+1)*m]
}
Что касается первого метода, выполнение последовательных вызовов make
не гарантирует, что у вас будет непрерывная матрица, поэтому вы можете разделить матрицу в памяти. Давайте рассмотрим пример с двумя подпрограммами Go, которые могут вызвать это:
- Подпрограмма # 0 запускает
make([][]int, n)
чтобы получить выделенную память для matrix
, получая часть памяти от 0x000 до 0x07F. - Затем он запускает цикл и выполняет первую строку
make([]int, m)
, получая значение от 0x080 до 0x0FF. - Во второй итерации планировщик вытесняет ее.
- Планировщик передает процессор подпрограмме # 1, и он начинает работать. Этот также использует
make
(для своих собственных целей) и получает от 0x100 до 0x17F (прямо рядом с первым рядом подпрограммы # 0). - Через некоторое время он прерывается, и процедура # 0 снова запускается.
- Он выполняет
make([]int, m)
соответствующую второй итерации цикла, и получает значение от 0x180 до 0x1FF для второй строки. На данный момент мы уже получили два разделенных ряда.
Во втором методе подпрограмма make([]int, n*m)
получает всю матрицу, выделенную в одном срезе, обеспечивая непрерывность. После этого необходим цикл для обновления указателей матрицы на субликаты, соответствующие каждой строке.
Вы можете поиграть с кодом, показанным выше, на игровой площадке Go, чтобы увидеть разницу в памяти, назначенной с помощью обоих методов. Обратите внимание, что я использовал runtime.Gosched()
только с целью получения процессора и принудительного переключения планировщика на другую подпрограмму.
Какой использовать? Представьте себе наихудший случай с первым методом, то есть каждая строка не находится в памяти рядом с другой строкой. Затем, если ваша программа выполняет итерации по элементам матрицы (для чтения или записи), вероятно, будет больше пропусков кэша (а следовательно, и более высокая задержка) по сравнению со вторым методом из-за худшей локальности данных. С другой стороны, при использовании второго метода может оказаться невозможным получить один фрагмент памяти, выделенный для матрицы, из-за фрагментации памяти (фрагменты распределяются по всей памяти), хотя теоретически для этого может быть достаточно свободной памяти,
Поэтому, если нет большой фрагментации памяти и матрицы, которая будет выделена, достаточно велика, вы всегда захотите использовать второй метод, чтобы получить преимущество от локальности данных.
Ответ 3
Вы можете ссылаться на этот фрагмент кода -
package main
import "fmt"
func main() {
var row, col int
fmt.Print("enter rows cols: ")
fmt.Scan(&row, &col)
// allocate composed 2d array
a := make([][]int, row)
for i := range a {
a[i] = make([]int, col)
}
// array elements initialized to 0
fmt.Println("a[0][0] =", a[0][0])
// assign
a[row-1][col-1] = 7
// retrieve
fmt.Printf("a[%d][%d] = %d\n", row-1, col-1, a[row-1][col-1])
// remove only reference
a = nil
// memory allocated earlier with make can now be garbage collected.
}
Ссылка
Ответ 4
Я написал эту функцию для замены строк двумерного массива:
func lchange(l1 int, l2 int, a [][]float64) {
b := make([]float64, 1)
b = a[l1][:]
a[l1] = a[l2]
a[l2] = b
}