Arreglos multidimensionales en Go

Go 语言数组Arreglos en Go

Un arreglo multidimensional es un arreglo de arreglos; se puede imaginar comotabla de datosomatriz, donde se puede acceder a cada elemento mediante múltiples índices.

En Go, los arreglos multidimensionales se pueden usar para procesar información estructurada, como datos tabulares, operaciones con matrices o tableros de juego.

Conceptos básicos

  • Arreglo unidimensional: como puntos en una línea recta, solo se necesita una coordenada (índice) para encontrar un elemento específico.
  • Arreglo bidimensional: como una tabla, se necesitan dos coordenadas: fila y columna.
  • Arreglo tridimensional: como un cubo, se necesitan tres coordenadas: largo, ancho y alto.
  • Dimensiones superiores: en teoría puede haber muchas dimensiones, pero en la programación real, las bidimensionales y tridimensionales son las más usadas.

Se puede comparar con nuestras plazas de aparcamiento habituales:

  • Arreglo unidimensional= una fila de plazas de aparcamiento (solo se necesita el número de plaza)
  • Arreglo bidimensional= un aparcamiento de varios niveles (se necesitan el nivel y el número de plaza)
  • Arreglo tridimensional= varios aparcamientos de varios niveles (se necesitan el número de aparcamiento, el nivel y el número de plaza)

Go admite arreglos multidimensionales. A continuación se muestran las formas habituales de declararlos:

var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type

Declaración e inicialización:

// Declarar un arreglo bidimensional var nombreArreglo [numFilas][numColumnas]tipoElemento // Declarar e inicializar var nombreArreglo [numFilas][numColumnas]tipoElemento = [numFilas][numColumnas]tipoElemento{valoresIniciales}

El siguiente ejemplo declara un arreglo entero tridimensional:

var threedim [5][10][4]int

Arreglo bidimensional

El arreglo bidimensional es el más simple de los arreglos multidimensionales; en esencia, está compuesto por arreglos unidimensionales. La forma de definir un arreglo bidimensional es la siguiente:

var arrayName [ x ][ y ] variable_type

variable_type es el tipo de dato en Go, arrayName es el nombre del arreglo. Un arreglo bidimensional puede considerarse una tabla, donde x es la fila e y es la columna. La siguiente figura muestra un arreglo bidimensional a de tres filas y cuatro columnas:

A los elementos de un arreglo bidimensional se puede acceder mediantea[ i ][ j ]para acceder.

Ejemplo

package main

import "fmt"

func main() {
    // Paso 1: Crear el arreglo
    values := [][]int{}

    // Paso 2: Usar la función append() para agregar dos arreglos unidimensionales como filas al arreglo bidimensional vacío
    row1 := []int{1, 2, 3}
    row2 := []int{4, 5, 6}
    values = append(values, row1)
    values = append(values, row2)

    // Paso 3: Mostrar las dos filas de datos
    fmt.Println("Row 1")
    fmt.Println(values[0])
    fmt.Println("Row 2")
    fmt.Println(values[1])

    // Paso 4: Acceder al primer elemento
    fmt.Println("El primer elemento es:")
    fmt.Println(values[0][0])
}

El resultado de ejecutar el ejemplo anterior es:

Row 1
[1 2 3]
Row 2
[4 5 6]
第一个元素为:
1

Inicializar un arreglo bidimensional

Los arreglos multidimensionales se pueden inicializar con llaves. El siguiente ejemplo es un arreglo bidimensional de 3 filas y 4 columnas:

a := [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11},   /* 第三行索引为 2 */
}
Nota:En el código anterior, la penúltima línea}debe tener una coma, porque la última línea}no puede estar en una línea por sí sola; también se puede escribir así:
a := [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11}}   /* 第三行索引为 2 */

El siguiente ejemplo inicializa un arreglo bidimensional de 2 filas y 2 columnas:

Ejemplo

package main

import "fmt"

func main() {
    // Crear un arreglo bidimensional
    sites := [2][2]string{}

    // Agregar elementos al arreglo bidimensional
    sites[0][0] = "Google"
    sites[0][1] = "Ejemplo"
    sites[1][0] = "Taobao"
    sites[1][1] = "Weibo"

    // Mostrar el resultado
    fmt.Println(sites)
}

El resultado de ejecutar el ejemplo anterior es:

[[Google Ejemplo] [Taobao Weibo]]

Acceder a un arreglo bidimensional

Se accede a un arreglo bidimensional especificando coordenadas, como el índice de fila y el índice de columna; por ejemplo:

val := a[2][3]
o 
var value int = a[2][3]

El ejemplo anterior accede al cuarto elemento de la tercera fila del arreglo bidimensional val.

Se pueden usar bucles anidados para mostrar los elementos de un arreglo bidimensional:

Ejemplo

package main

import "fmt"

func main() {
   /* Arreglo - 5 filas y 2 columnas */
   var a = [5][2]int{ {0,0}, {1,2}, {2,4}, {3,6},{4,8}}
   var i, j int

   /* Imprimir los elementos del arreglo */
   for  i = 0; i < 5; i++ {
      for j = 0; j < 2; j++ {
         fmt.Printf("a[%d][%d] = %d\n", i,j, a[i][j] )
      }
   }
}

El resultado de ejecutar el ejemplo anterior es:

a[0][0] = 0
a[0][1] = 0
a[1][0] = 1
a[1][1] = 2
a[2][0] = 2
a[2][1] = 4
a[3][0] = 3
a[3][1] = 6
a[4][0] = 4
a[4][1] = 8

El siguiente ejemplo crea un arreglo multidimensional con distinta cantidad de elementos en cada dimensión:

Ejemplo

package main

import "fmt"

func main() {
    // Crear un arreglo bidimensional vacío
    animals := [][]string{}

    // Crear tres arreglos unidimensionales, cada uno con longitud diferente
    row1 := []string{"fish", "shark", "eel"}
    row2 := []string{"bird"}
    row3 := []string{"lizard", "salamander"}

    // Usar la función append() para agregar los arreglos unidimensionales al arreglo bidimensional
    animals = append(animals, row1)
    animals = append(animals, row2)
    animals = append(animals, row3)

    // Imprimir en un bucle
    for i := range animals {
        fmt.Printf("Row: %v\n", i)
        fmt.Println(animals[i])
    }
}

El resultado de ejecutar el ejemplo anterior es:

Row: 0
[fish shark eel]
Row: 1
[bird]
Row: 2
[lizard salamander]

Acceder y modificar elementos del arreglo

Acceder a los elementos

Ejemplo

package main

import "fmt"

func main() {
    // Crear una matriz de 3x3
    matrix := [3][3]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }
   
    // Acceder a un solo elemento
    fmt.Println("Fila 1, Columna 2:", matrix[0][1])  // Imprime: 2
    fmt.Println("Fila 3, Columna 3:", matrix[2][2]) // Imprime: 9
   
    // Acceder a toda la fila
    fmt.Println("Fila 2:", matrix[1])  // Imprime: [4 5 6]
   
    // Recorrer todos los elementos
    fmt.Println("\nRecorrer todos los elementos:")
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            fmt.Printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j])
        }
    }
}

Modificar elementos

Ejemplo

package main

import "fmt"

func main() {
    // Crear una matriz de ceros de 2x2
    var grid [2][2]int
   
    fmt.Println("Matriz antes de modificar:", grid)
   
    // Modificar el elemento en una posición específica
    grid[0][0] = 10
    grid[0][1] = 20
    grid[1][0] = 30
    grid[1][1] = 40
   
    fmt.Println("Matriz después de modificar:", grid)
   
    // Modificar una fila en lote
    grid[0] = [2]int{100, 200}
    fmt.Println("Después de modificar la primera fila:", grid)
}

Arreglos tridimensionales y de dimensiones superiores

Ejemplo de arreglo tridimensional

Ejemplo

package main

import "fmt"

func main() {
    // Declarar un arreglo tridimensional de 2x3x4
    // Se puede entender como: 2 planos, cada plano con 3 filas y 4 columnas
    var cube [2][3][4]int
   
    // Inicializar el arreglo tridimensional
    cube = [2][3][4]int{
        { // El primer plano
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12},
        },
        { // El segundo plano
            {13, 14, 15, 16},
            {17, 18, 19, 20},
            {21, 22, 23, 24},
        },
    }
   
    // Acceder a los elementos del arreglo tridimensional
    fmt.Println("cube[0][1][2] =", cube[0][1][2])  // Salida: 7
    fmt.Println("cube[1][2][3] =", cube[1][2][3])  // Salida: 24
   
    // Recorrer el arreglo tridimensional
    fmt.Println("\nContenido del arreglo tridimensional:")
    for i := 0; i < 2; i++ {
        fmt.Printf("Plano %d:\n", i)
        for j := 0; j < 3; j++ {
            for k := 0; k < 4; k++ {
                fmt.Printf("%3d ", cube[i][j][k])
            }
            fmt.Println()
        }
        fmt.Println()
    }
}

Vea la siguiente figura:


Operaciones comunes con arreglos multidimensionales

1. Recorrer con range

Ejemplo

package main

import "fmt"

func main() {
    // Crear un arreglo bidimensional
    scores := [3][4]int{
        {85, 90, 78, 92},
        {88, 76, 95, 89},
        {92, 85, 88, 90},
    }
   
    fmt.Println("Tabla de calificaciones de estudiantes:")
   
    // Usar range para recorrer un arreglo bidimensional
    for i, row := range scores {
        fmt.Printf("Calificaciones del estudiante %d: ", i+1)
        for j, score := range row {
            fmt.Printf("%d ", score)
            // Si se necesitan tanto el índice como el valor
            _ = j // Evitar la advertencia de variable no utilizada
        }
        fmt.Println()
    }
   
    // Solo importa el valor, no el índice
    total := 0
    count := 0
    for _, row := range scores {
        for _, score := range row {
            total += score
            count++
        }
    }
    fmt.Printf("\nPromedio: %.2f\n", float64(total)/float64(count))
}

2. Obtener la longitud del arreglo

Ejemplo

package main

import "fmt"

func main() {
    // Crear un arreglo multidimensional irregular
    jagged := [3][3]int{
        {1, 2, 3},
        {4, 5},
        {6, 7, 8, 9}, // Nota: Esto causará un error de compilación, porque cada fila debe tener la misma longitud
    }
   
    // Ejemplo correcto: obtener las dimensiones del arreglo
    matrix := [4][5]int{}
   
    // Obtener el número de filas
    rows := len(matrix)
    fmt.Println("Número de filas:", rows)  // Salida: 4
   
    // Obtener el número de columnas de la primera fila (todas las filas tienen la misma longitud)
    cols := len(matrix[0])
    fmt.Println("Número de columnas:", cols)  // Salida: 5
   
    // Obtener el número total de elementos
    totalElements := rows * cols
    fmt.Println("Número total de elementos:", totalElements)  // Salida: 20
}

3. Comparación de arreglos

Ejemplo

package main

import "fmt"

func main() {
    // Crear dos arreglos bidimensionales idénticos
    a := [2][2]int{{1, 2}, {3, 4}}
    b := [2][2]int{{1, 2}, {3, 4}}
    c := [2][2]int{{1, 2}, {3, 5}}
   
    // Los arreglos se pueden comparar directamente (solo cuando las dimensiones son exactamente iguales)
    fmt.Println("a == b:", a == b)  // Salida: true
    fmt.Println("a == c:", a == c)  // Salida: false
   
    // Nota: los arrays de diferentes dimensiones no se pueden comparar
    // d := [2][3]int{{1, 2, 3}, {4, 5, 6}}
    // fmt.Println(a == d) // Error de compilación: tipos no coinciden
}

Casos de uso prácticos

Escenario 1: Tablero de juego (tres en raya)

Ejemplo

package main

import "fmt"

func main() {
    // Inicializar un tablero de tres en raya de 3x3
    var board [3][3]string
   
    // Inicializar vacío
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            board[i][j] = " "
        }
    }
   
    // Simular jugada
    board[0][0] = "X"
    board[1][1] = "O"
    board[2][2] = "X"
   
    // Imprimir tablero
    fmt.Println("Tablero de tres en raya:")
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            fmt.Printf(" %s ", board[i][j])
            if j < 2 {
                fmt.Printf("|")
            }
        }
        fmt.Println()
        if i < 2 {
            fmt.Println("---+---+---")
        }
    }
}

Escenario 2: Sistema de gestión de calificaciones de estudiantes

Ejemplo

package main

import "fmt"

func main() {
    // Definir: 3 estudiantes, cada uno con 4 cursos
    var grades [3][4]float64
   
    // Ingresar calificaciones de estudiantes
    grades = [3][4]float64{
        {85.5, 90.0, 78.5, 92.0},  // Calificaciones del estudiante 1
        {88.0, 76.5, 95.0, 89.5},  // Calificaciones del estudiante 2
        {92.5, 85.0, 88.5, 90.0},  // Calificaciones del estudiante 3
    }
   
    // Calcular el promedio de cada estudiante
    fmt.Println("Estadísticas de calificaciones de estudiantes:")
    for i, studentGrades := range grades {
        sum := 0.0
        for _, grade := range studentGrades {
            sum += grade
        }
        average := sum / float64(len(studentGrades))
        fmt.Printf("Estudiante %d: Promedio = %.2f\n", i+1, average)
    }
   
    // Calcular el promedio de cada curso
    fmt.Println("\nPromedio de los cursos:)
    for j := 0; j < 4; j++ {
        sum := 0.0
        for i := 0; i < 3; i++ {
            sum += grades[i][j]
        }
        average := sum / 3.0
        fmt.Printf("Curso %d: Promedio = %.2f\n", j+1, average)
    }
}

Escenario 3: Procesamiento de píxeles de imagen

Ejemplo

package main

import "fmt"

func main() {
    // Simular una imagen en escala de grises simple de 3x3
    // Rango de valores de cada píxel: 0 (negro) ~ 255 (blanco)
    var image [3][3]int
   
    // Inicializar la imagen (un degradado simple)
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            image[i][j] = (i + j) * 50
        }
    }
   
    // Mostrar la imagen original
    fmt.Println("Imagen original:")
    displayImage(image)
   
    // Procesamiento de imagen: aumentar brillo
    fmt.Println("\nImagen con brillo aumentado:")
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            // Aumentar el brillo, pero sin superar 255
            newValue := image[i][j] + 50
            if newValue > 255 {
                newValue = 255
            }
            image[i][j] = newValue
        }
    }
    displayImage(image)
}

func displayImage(img [3][3]int) {
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            fmt.Printf("%3d ", img[i][j])
        }
        fmt.Println()
    }
}

Notas y mejores prácticas

1. La longitud del arreglo es parte del tipo

Ejemplo

package main

import "fmt"

func main() {
    // ¡Estos dos son de tipos diferentes!
    var a [2][3]int
    var b [3][2]int
   
    // El siguiente código dará error de compilación
    // a = b // Error: tipos no coinciden
   
    fmt.Printf("Tipo de a: %T\n", a)  // [2][3]int
    fmt.Printf("Tipo de b: %T\n", b)  // [3][2]int
}

2. Tipo de valor vs tipo de referencia

Ejemplo

package main

import "fmt"

func main() {
    // Los arrays son tipos de valor
    original := [2][2]int{{1, 2}, {3, 4}}
   
    // La asignación crea una copia
    copy := original
   
    // Modificar la copia no afecta al array original
    copy[0][0] = 100
   
    fmt.Println("Array original:", original)  // [[1 2] [3 4]]
    fmt.Println("Array copia:", copy)      // [[100 2] [3 4]]
   
    // Si se necesita semántica de referencia, se pueden usar slices (se presentará en artículos posteriores)
}

3. Consideraciones de rendimiento

  • Memoria contigua: Los arrays multidimensionales se almacenan de forma continua en memoria, con acceso rápido
  • Tamaño fijo: La longitud del array se determina en tiempo de compilación y no se puede cambiar dinámicamente
  • Escenarios adecuados: Cuando el tamaño de los datos es conocido y fijo, los arrays son la mejor opción

Preguntas frecuentes

P1: ¿Cuál es la diferencia entre un arreglo multidimensional y un slice anidado?

Característica Array multidimensional Slice anidado
Tamaño Fijo, determinado en tiempo de compilación Dinámico, variable en tiempo de ejecución
Memoria Asignación contigua Puede ser no contigua
Rendimiento Acceso rápido Un poco más lento, con sobrecarga adicional
Escenario de uso Tamaño de datos conocido Tamaño de datos variable

P2: ¿Cómo crear un arreglo bidimensional irregular?

Los arrays de Go requieren que cada fila tenga la misma longitud. Si necesitas una estructura irregular, debes usar slices:

Ejemplo

// Usar slices para crear una estructura irregular
irregular := [][]int{
    {1, 2, 3},
    {4, 5},          // Esta fila solo tiene 2 elementos
    {6, 7, 8, 9},    // Esta fila tiene 4 elementos
}

P3: ¿Puede un arreglo multidimensional usarse como parámetro de función?

Sí, pero ten en cuenta que los arrays son tipos de valor; pasar arrays grandes tiene un costo de rendimiento:

Ejemplo

func processMatrix(matrix [3][3]int) [3][3]int {
    // Procesar la matriz...
    matrix[0][0] = 100
    return matrix
}

// Una mejor opción es usar punteros o slices
func processMatrixPtr(matrix *[3][3]int) {
    matrix[0][0] = 100
}

Go 语言数组Arrays en Go

Otras extensiones