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


103

Estoy aprendiendo a Go pasando por A Tour of Go . Uno de los ejercicios allí me pide que cree una porción 2D de dyfilas y dxcolumnas 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 iterar a través de 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. ¿Existe una forma concisa de inicializar cortes 2D (o n-dimensionales) en Go?

Respuestas:


148

No hay una forma 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 matriz bidimensional .

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

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 , lo obtiene "gratis", 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 hay que enumerar todos los elementos; pero hay algunos trucos, es decir, no es necesario enumerar todos los valores, solo los que no son los valores cero del tipo de elemento del segmento. Para obtener más detalles sobre esto, consulte Elementos con clave en la inicialización de la matriz golang .

Por ejemplo, si desea un segmento donde los primeros 10 elementos son ceros, y luego sigue 1y 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 matrices en lugar de porciones , se puede crear muy fácilmente:

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

La 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 matrices, no es necesario 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 (y strings): The Mechanics of 'append' para obtener más detalles.

Pruebe los ejemplos en Go Playground .


Dado que usar una matriz simplifica el código, me gustaría hacer eso. ¿Cómo se especifica eso en una estructura? Obtengo cannot use [5][2]string literal (type [5][2]string) as type [][]string in field valuecuando trato de asignar la matriz a lo que supongo que le estoy diciendo a Go es una porción.
Eric Lindsey

Lo descubrí yo mismo y edité la respuesta para agregar la información.
Eric Lindsey

1
@EricLindsey Si bien su edición es buena, todavía la rechazaré porque no quiero alentar el uso de matrices solo porque la inicialización es más fácil. En Go, las matrices son secundarias, las rodajas son el camino a seguir. Para obtener más información, consulte ¿Cuál es la forma más rápida de agregar una matriz a otra en Go? Las matrices también tienen su lugar, para obtener más detalles, consulte ¿Por qué tener matrices en Go?
icza

bastante justo, pero creo que la información todavía tiene mérito. Lo que estaba tratando de explicar con mi edición es que si necesita la flexibilidad de diferentes dimensiones entre objetos, entonces los cortes son el camino a seguir. Por otro lado, si su información está estructurada de manera rígida y siempre será la misma, entonces las matrices no solo son más fáciles de inicializar, sino que también son más eficientes. ¿Cómo podría mejorar la edición?
Eric Lindsey

@EricLindsey Veo que hiciste otra edición que ya fue rechazada por otros. En su edición, estaba diciendo que use matrices para tener un acceso más rápido a los elementos. Tenga en cuenta que Go optimiza muchas cosas, y puede que este no sea el caso, los cortes pueden ser igual de rápidos. Para obtener más información, consulte Array vs Slice: velocidad de acceso .
icza

12

Hay dos formas de utilizar porciones 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]
}

En lo que respecta al primer método, hacer makellamadas 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 de Go que podrían causar esto:

  1. La rutina # 0 se ejecuta make([][]int, n)para obtener memoria asignada para matrixobtener una parte de la memoria de 0x000 a 0x07F.
  2. Luego, inicia el ciclo y hace la primera fila make([]int, m), pasando de 0x080 a 0x0FF.
  3. En la segunda iteración, el planificador lo reemplaza.
  4. El planificador le da al procesador la rutina # 1 y comienza a ejecutarse. Este también usa make(para sus propios fines) y pasa de 0x100 a 0x17F (justo al lado de la primera fila de la rutina # 0).
  5. Después de un tiempo, se reemplaza y la rutina # 0 comienza a ejecutarse nuevamente.
  6. Hace lo que make([]int, m)corresponde a la segunda iteración del ciclo y pasa 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)que toda la matriz se asigne en un solo segmento, lo que garantiza la contigüidad. Después de eso, se necesita un bucle para actualizar los punteros de la matriz a las sublices 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 utilicé runtime.Gosched()solo con el propósito de ceder el procesador y forzar al programador a cambiar a otra rutina.

Cual usar? Imagine el peor de los casos con el primer método, es decir, cada fila no es la siguiente en la 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 fallas de caché (por lo tanto, mayor latencia) 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 repartidos por toda la memoria), aunque teóricamente puede haber suficiente memoria libre para ello. .

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


2
golang.org/doc/effective_go.html#slices muestra una forma inteligente de hacer la técnica de memoria contigua aprovechando la sintaxis nativa del segmento (por ejemplo, no es necesario calcular explícitamente los límites del segmento con expresiones como (i + 1) * m)
Magnus

0

En respuestas anteriores no consideramos la situación cuando se desconoce la longitud inicial. Para este caso, puede usar la siguiente lógica para crear una matriz

items := []string{"1.0", "1.0.1", "1.0.2", "1.0.2.1.0"}
mx := make([][]string, 0)
for _, item := range items {
    ind := strings.Count(item, ".")
    for len(mx) < ind+1 {
        mx = append(mx, make([]string, 0))
    }
    mx[ind] = append(mx[ind], item)

}

fmt.Println(mx)

https://play.golang.org/p/pHgggHr4nbB


1
No estoy seguro de si esto está dentro de los límites de OP de "manera concisa", como dijo "Creo que iterar a través de cada segmento para inicializarlo es demasiado detallado".
Marcos Canales Mayo
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.