www gorenew espanol go slice

gorenew - ¿Cuál es una forma concisa de crear un corte 2D en Go?



gorenew.com registration (3)

Estoy aprendiendo Go pasando por A Tour of Go . Uno de los ejercicios allí me pide que cree una porción 2D de filas dx columnas dx que contengan uint8 . Mi enfoque actual, que funciona, es este:

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 }

Creo que recorrer cada segmento para inicializarlo es demasiado detallado. Y si el segmento tuviera más dimensiones, el código se volvería difícil de manejar. ¿Hay una manera concisa de inicializar sectores 2D (o n-dimensionales) en Go?


Hay dos formas de usar sectores para crear una matriz. Echemos un vistazo a las diferencias entre ellos.

Primer método:

matrix := make([][]int, n) for i := 0; i < n; i++ { matrix[i] = make([]int, m) }

Segundo método:

matrix := make([][]int, n) rows := make([]int, n*m) for i := 0; i < n; i++ { matrix[i] = rows[i*m : (i+1)*m] }

Con respecto al primer método, make llamadas sucesivas no garantiza que terminará con una matriz contigua, por lo que puede tener la matriz dividida en la memoria. Pensemos en un ejemplo con dos rutinas Go que podrían causar esto:

  1. La rutina # 0 ejecuta make([][]int, n) para obtener memoria asignada para la matrix , obteniendo un trozo de memoria de 0x000 a 0x07F.
  2. Luego, inicia el bucle y realiza la primera fila make([]int, m) , pasando de 0x080 a 0x0FF.
  3. En la segunda iteración, el planificador se adelanta.
  4. El programador le da al procesador la rutina n. ° 1 y comienza a ejecutarse. Este también usa make (para sus propios fines) y obtiene de 0x100 a 0x17F (justo al lado de la primera fila de la rutina # 0).
  5. Después de un tiempo, se adelanta y la rutina # 0 comienza a ejecutarse nuevamente.
  6. Realiza el make([]int, m) correspondiente a la segunda iteración del ciclo y obtiene de 0x180 a 0x1FF para la segunda fila. En este punto, ya tenemos dos filas divididas.

Con el segundo método, la rutina hace make([]int, n*m) obtenga toda la matriz asignada en un solo segmento, asegurando la contigüidad. Después de eso, se necesita un bucle para actualizar los punteros de la matriz a los subslices correspondientes a cada fila.

Puedes jugar con el código que se muestra arriba en Go Playground para ver la diferencia en la memoria asignada usando ambos métodos. Tenga en cuenta que usé runtime.Gosched() solo con el propósito de ceder el procesador y obligar al programador a cambiar a otra rutina.

¿Cuál usar? Imagine el peor de los casos con el primer método, es decir, cada fila no es la siguiente en memoria a otra fila. Luego, si su programa itera a través de los elementos de la matriz (para leerlos o escribirlos), probablemente habrá más errores de caché (por lo tanto, una latencia más alta) en comparación con el segundo método debido a una peor localidad de datos. Por otro lado, con el segundo método puede que no sea posible obtener una sola pieza de memoria asignada para la matriz, debido a la fragmentación de la memoria (fragmentos diseminados por toda la memoria), aunque en teoría puede haber suficiente memoria libre para ello. .

Por lo tanto, a menos que haya mucha fragmentación de memoria y la matriz que se asignará sea lo suficientemente grande, siempre querrá usar el segundo método para aprovechar la localidad de datos.


No hay una manera más concisa, lo que hiciste es la forma "correcta"; porque los cortes son siempre unidimensionales pero pueden estar compuestos para construir objetos de dimensiones superiores. Consulte esta pregunta para obtener más detalles: Ir: ¿Cómo es la representación de memoria de la matriz bidimensional ?

Una cosa que puede simplificar es usar la construcción for range :

a := make([][]uint8, dy) for i := range a { a[i] = make([]uint8, dx) }

También tenga en cuenta que si inicializa su segmento con un literal compuesto , obtendrá esto de forma "gratuita", por ejemplo:

a := [][]uint8{ {0, 1, 2, 3}, {4, 5, 6, 7}, } fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]

Sí, esto tiene sus límites, ya que aparentemente tiene que enumerar todos los elementos; pero hay algunos trucos, a saber, no tiene que enumerar todos los valores, solo los que no son los valores cero del tipo de elemento del segmento. Para obtener más detalles al respecto, consulte Elementos con clave en la inicialización de la matriz de golang .

Por ejemplo, si desea un segmento donde los primeros 10 elementos son ceros, y luego sigue a 1 y 2 , se puede crear así:

b := []uint{10: 1, 2} fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]

También tenga en cuenta que si usa arrays lugar de slices , se puede crear muy fácilmente:

c := [5][5]uint8{} fmt.Println(c)

Salida es:

[[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]]

En el caso de las matrices, no tiene que iterar sobre la matriz "externa" e inicializar las matrices "internas", ya que las matrices no son descriptores sino valores. Consulte la publicación de blog Arrays, slices (and strings): The Mechanics of ''append'' para más detalles.

Pruebe los ejemplos en Go Playground .


Puedes referir esta pieza de código -

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. }

Reference