Arrays y Slices
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)
}
| Expresion | Longitud | Capacidad |
|---|---|---|
s[low:high] | high - low | cap(s) - low |
s[low:high:max] | high - low | max - 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
- Escribe una funcion que rote un slice
nposiciones a la izquierda - Implementa
filtrar(s []int, fn func(int) bool) []intque retorne solo los elementos que cumplen la condicion - Crea una funcion que combine dos slices ordenados en uno solo manteniendo el orden
Resumen
- Arrays tienen tamanio fijo y se copian por valor:
[5]int - Slices son referencias a arrays subyacentes:
[]int make([]T, len, cap)crea slices con capacidad preasignadaappend()agrega elementos y puede reasignar el arraycopy()crea copias independientes[low:high:max]controla longitud y capacidadnilslice es idiomatico para slices vacios- Paquete
slices(Go 1.21+) simplifica operaciones comunes