← Volver al listado de tecnologías

Arrays y Slices

Por: Artiko
goarraysslicesappendmake

Arrays y Slices

Go tiene dos tipos de secuencias: arrays (tamanio fijo) y slices (tamanio dinamico). En la practica los slices se usan casi siempre; los arrays son la base sobre la que funcionan.

Arrays: Tamanio Fijo

El tamanio de un array es parte de su tipo. [3]int y [5]int son tipos diferentes:

package main

import "fmt"

func main() {
    // Declaracion con zero values
    var a [5]int
    fmt.Println(a) // [0 0 0 0 0]

    // Literal
    b := [3]string{"go", "rust", "zig"}
    fmt.Println(b) // [go rust zig]

    // Tamanio inferido con ...
    c := [...]int{10, 20, 30, 40}
    fmt.Println(len(c)) // 4

    // Inicializar indices especificos
    d := [5]int{1: 10, 3: 30}
    fmt.Println(d) // [0 10 0 30 0]

    // Los arrays se copian por valor
    e := b
    e[0] = "python"
    fmt.Println(b[0]) // "go" (b no cambio)
    fmt.Println(e[0]) // "python"
}

Los arrays se comparan con == si sus elementos son comparables:

package main

import "fmt"

func main() {
    a := [3]int{1, 2, 3}
    b := [3]int{1, 2, 3}
    c := [3]int{1, 2, 4}

    fmt.Println(a == b) // true
    fmt.Println(a == c) // false
}

Slices: Dinamicos y Flexibles

Un slice es una referencia a una seccion de un array subyacente. Tiene tres componentes: puntero, longitud y capacidad.

package main

import "fmt"

func main() {
    // Literal de slice (sin tamanio)
    s := []int{1, 2, 3, 4, 5}
    fmt.Println(s)      // [1 2 3 4 5]
    fmt.Println(len(s)) // 5
    fmt.Println(cap(s)) // 5

    // Slice desde un array
    arr := [5]string{"a", "b", "c", "d", "e"}
    sl := arr[1:4] // ["b", "c", "d"]
    fmt.Println(sl)

    // Modificar el slice modifica el array original
    sl[0] = "X"
    fmt.Println(arr) // [a X c d e]
}

Internals: Pointer, Length, Capacity

Un slice es una estructura de tres campos:

┌─────────┬────────┬──────────┐
│ pointer │ length │ capacity │
└────┬────┴────────┴──────────┘


┌───┬───┬───┬───┬───┐
│ a │ b │ c │ d │ e │  ← array subyacente
└───┴───┴───┴───┴───┘
package main

import "fmt"

func main() {
    arr := [6]int{10, 20, 30, 40, 50, 60}

    s := arr[1:4] // [20, 30, 40]
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
    // len=3 cap=5 [20 30 40]
    // cap=5 porque desde indice 1 hasta el final del array hay 5 elementos
}

make(): Crear Slices con Capacidad

make crea un slice con longitud y capacidad especificadas:

package main

import "fmt"

func main() {
    // make(tipo, longitud, capacidad)
    s := make([]int, 3, 10)
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
    // len=3 cap=10 [0 0 0]

    // Sin capacidad explicita: cap == len
    s2 := make([]string, 5)
    fmt.Printf("len=%d cap=%d\n", len(s2), cap(s2))
    // len=5 cap=5
}

append(): Agregar Elementos

append retorna un nuevo slice. Si la capacidad no alcanza, Go asigna un array mas grande:

package main

import "fmt"

func main() {
    var s []int // nil slice
    fmt.Println(s == nil) // true

    s = append(s, 1)
    s = append(s, 2, 3)
    s = append(s, []int{4, 5}...) // expandir otro slice
    fmt.Println(s) // [1 2 3 4 5]

    // Observar crecimiento de capacidad
    var nums []int
    for i := 0; i < 10; i++ {
        nums = append(nums, i)
        fmt.Printf("len=%2d cap=%2d %v\n", len(nums), cap(nums), nums)
    }
}

La capacidad crece aproximadamente al doble cada vez que se necesita mas espacio.

Slicing Syntax: [low:high:max]

La forma de tres indices [low:high:max] controla tanto la longitud como la capacidad del slice resultante:

package main

import "fmt"

func main() {
    arr := [6]int{0, 1, 2, 3, 4, 5}

    // [low:high] - cap incluye hasta el final del array
    s1 := arr[1:4]
    fmt.Printf("s1: len=%d cap=%d %v\n", len(s1), cap(s1), s1)
    // s1: len=3 cap=5 [1 2 3]

    // [low:high:max] - cap = max - low
    s2 := arr[1:4:4]
    fmt.Printf("s2: len=%d cap=%d %v\n", len(s2), cap(s2), s2)
    // s2: len=3 cap=3 [1 2 3]

    // Util para evitar que append modifique el array original
    s3 := arr[1:3:3]
    s3 = append(s3, 99) // crea nuevo array subyacente
    fmt.Println(arr) // [0 1 2 3 4 5] (sin modificar)
}
ExpresionLongitudCapacidad
s[low:high]high - lowcap(s) - low
s[low:high:max]high - lowmax - low

copy()

copy copia elementos entre slices y retorna el numero de elementos copiados:

package main

import "fmt"

func main() {
    origen := []int{1, 2, 3, 4, 5}
    destino := make([]int, 3)

    n := copy(destino, origen)
    fmt.Println(destino) // [1 2 3]
    fmt.Println("Copiados:", n) // 3

    // Copia completa independiente
    clon := make([]int, len(origen))
    copy(clon, origen)
    clon[0] = 999
    fmt.Println(origen) // [1 2 3 4 5] (sin cambio)
    fmt.Println(clon)   // [999 2 3 4 5]
}

nil Slice vs Empty Slice

package main

import "fmt"

func main() {
    // nil slice: no tiene array subyacente
    var nilSlice []int
    fmt.Println(nilSlice == nil) // true
    fmt.Println(len(nilSlice))   // 0
    fmt.Println(cap(nilSlice))   // 0

    // Empty slice: tiene array pero longitud 0
    emptySlice := []int{}
    fmt.Println(emptySlice == nil) // false
    fmt.Println(len(emptySlice))   // 0

    // make con longitud 0
    madeSlice := make([]int, 0)
    fmt.Println(madeSlice == nil) // false

    // Ambos funcionan igual con append
    nilSlice = append(nilSlice, 1)
    emptySlice = append(emptySlice, 1)
    fmt.Println(nilSlice)   // [1]
    fmt.Println(emptySlice) // [1]
}

En la practica, var s []int (nil) es lo idiomatico para declarar un slice vacio.

Eliminar Elementos

Go no tiene funcion built-in para eliminar. Se usa slicing + append:

package main

import "fmt"

func eliminar(s []int, i int) []int {
    return append(s[:i], s[i+1:]...)
}

func eliminarSinOrden(s []int, i int) []int {
    s[i] = s[len(s)-1]
    return s[:len(s)-1]
}

func main() {
    // Mantiene orden
    s := []int{10, 20, 30, 40, 50}
    s = eliminar(s, 2) // eliminar 30
    fmt.Println(s) // [10 20 40 50]

    // Sin mantener orden (mas eficiente)
    s2 := []int{10, 20, 30, 40, 50}
    s2 = eliminarSinOrden(s2, 1) // eliminar 20
    fmt.Println(s2) // [10 50 30 40]
}

Paquete slices (Go 1.21+)

Desde Go 1.21, el paquete slices provee funciones genericas para operaciones comunes:

package main

import (
    "fmt"
    "slices"
)

func main() {
    nums := []int{5, 3, 1, 4, 2}

    // Ordenar
    slices.Sort(nums)
    fmt.Println(nums) // [1 2 3 4 5]

    // Buscar
    fmt.Println(slices.Contains(nums, 3)) // true
    fmt.Println(slices.Contains(nums, 9)) // false

    // Indice
    fmt.Println(slices.Index(nums, 4)) // 3

    // Comparar
    a := []int{1, 2, 3}
    b := []int{1, 2, 3}
    fmt.Println(slices.Equal(a, b)) // true

    // Min y Max
    fmt.Println(slices.Min(nums)) // 1
    fmt.Println(slices.Max(nums)) // 5

    // Reverse
    slices.Reverse(nums)
    fmt.Println(nums) // [5 4 3 2 1]

    // Compactar (eliminar duplicados consecutivos en slice ordenado)
    dup := []int{1, 1, 2, 2, 3, 3, 3}
    dup = slices.Compact(dup)
    fmt.Println(dup) // [1 2 3]
}

Ejercicios

  1. Escribe una funcion que rote un slice n posiciones a la izquierda
  2. Implementa filtrar(s []int, fn func(int) bool) []int que retorne solo los elementos que cumplen la condicion
  3. Crea una funcion que combine dos slices ordenados en uno solo manteniendo el orden

Resumen