Skip to content

Slices en Go

Posted on:December 21, 2020聽at聽03:51 PM

Table of Contents

Open Table of Contents

Disclaimer

Muchos de los ejemplos y conceptos han sido directamente tomados y traducidos del libro The Go programming language, todo el cr茅dito para los autores, los muestro en este post 煤nicamente con fines did谩cticos para ense帽ar el lenguaje de programaci贸n Go.

驴Qu茅 es un Slice?

Un slice representa una secuencia de elementos de un mismo tipo de longitud variable. El tipo de un slice es escrito []T, donde los elementos son del tipo T; luce como un arreglo pero sin su tama帽o.

Los arreglos y los slices est谩n 铆ntimamente conectados. Un slice es una estructura de datos ligera que le da acceso a un subconjunto (o quiz谩 a todos) de elementos de un arreglo, el cual es conocido como el arreglo subyacente del slice o underlying array en ingl茅s.

package main

import "fmt"

func main()  {
  var slice []string
  fmt.Println(slice)
}

驴Qu茅 elementos forman un slice?

Un slice tiene tres componentes: un puntero, una longitud, y una capacidad.

Nota: las funciones len y cap nos permiten calcular la longitud y capacidad respectivamente de un slice.

package main

import "fmt"

func main()  {
  languages := []string{"go", "javascript", "php"}
  fmt.Println(languages)
  fmt.Println(len(languages)) // 3
  fmt.Println(cap(languages)) // 3
}

Representaci贸n del arreglo subyacente (underlying array)

Multiples slices pueden compartir un mismo arreglo subyacente y pueden referirse a partes superpuestas de ese arreglo. En la imagen debajo podemos ver un slice de strings que representa los meses del a帽o, y dos slices superpuestos de este.

Slices in go underlying array

Tal que Enero esta en months[1] y Diciembre en months[12].

Operador slice

El operador slice s[i:j], crea un nuevo slice que hace referencia a elementos de i a trav茅s de j-1 (se omite el 煤ltimo elemento) de la secuencia s, el cual puede ser un arreglo, un puntero a un arreglo, u otro slice.

El slice resultante contiene j-i elementos, si i es omitido su valor ser谩 0, y si j es omitido, su valor ser谩 len(s). As铆 que el slice months[1:13] hace referencia a el rango completo de meses v谩lidos, de igual forma el slice months[1:]; el slice months[:] hace referencia al arreglo completo.

package main

import "fmt"

func main()  {
  months := [...]string{
    0: "",
    1: "Enero",
    2: "Febrero",
    3: "Marzo",
    4: "Abril",
    5: "Mayo",
    6: "Junio",
    7: "Julio",
    8: "Agosto",
    9: "Septiembre",
    10: "Octubre",
    11: "Noviembre",
    12: "Diciembre",
  }

  fmt.Printf("%q\n", months[1:13]) // ["Enero" "Febrero" "Marzo" "Abril" "Mayo" "Junio" "Julio" "Agosto" "Septiembre" "Octubre" "Noviembre" "Diciembre"]
  fmt.Printf("%q\n", months[1:]) // ["Enero" "Febrero" "Marzo" "Abril" "Mayo" "Junio" "Julio" "Agosto" "Septiembre" "Octubre" "Noviembre" "Diciembre"]
  fmt.Printf("%q\n", months[:]) // ["" "Enero" "Febrero" "Marzo" "Abril" "Mayo" "Junio" "Julio" "Agosto" "Septiembre" "Octubre" "Noviembre" "Diciembre"]
}

Cuando se usa el operador slice m谩s all谩 de la capacidad del slice causa un panic, pero hacer slice m谩s all谩 de la longitud extiende el slice, entonces el resultado puede ser un slice m谩s grande que el original.

package main

import "fmt"

func main()  {
  // same definition for the months slice...

  summer := months[6:9]
  fmt.Println(summer[:20]) // panic: runtime error: slice bounds out of range [:20] with capacity 7

  endlessSummer := summer[:5] // extiende la capacidad el slice
  fmt.Printf("%q\n", endlessSummer) // ["Junio" "Julio" "Agosto" "Septiembre" "Octubre"]
}

Comparando slices

A diferencia de los arreglos, los slices no son comparables, por lo tanto no podemos usar el operador de comparaci贸n == para determinar si dos slices contienen los mismos elementos. La librer铆a est谩ndar de Go provee la funci贸n bytes.Equal altamente optimizada para comparar dos slices de tipo []byte, pero para los otros tipos de slice debemos hacer la comparaci贸n nosotros mismos.

package main

import "fmt"

func main()  {
  x := []string{"go", "php", "javascript"}
  y := []string{"go", "php", "javascript"}

  fmt.Printf("x == y : %t\n", equal(x, y)) // x == y : true
}

func equal(x, y []string) bool {
  if len(x) != len(y) {
    return false
  }

  for i := range x {
    if x[i] != y[i] {
      return false
    }
  }
  return true
}

La 煤nica comparaci贸n legal de un slice es compararlo contra nil, por ejemplo:

package main

import "fmt"

func main()  {
  // same definition for the months slice...

  summer := months[6:9]
  if summer == nil {
    fmt.Println("summer es igual a nil")
    return
  }
  // en este caso imprimir铆a esto porque summer no es igual a nil
  fmt.Println("summer no es igual a nil")
}

El valor zero de un tipo slice es nil. Un slice con valor nil no tiene un arreglo subyacente. El slice con valor nil tiene una longitud y capacidad cero, pero existen slices que no tienen valor nil con longitud y capacidad cero, por ejemplo []int{} o make([]int, 3)[3:]. Como sucede con cualquier tipo que pueda tener valores nil, el valor nil de un slice en particular puede ser escrito usando una expresi贸n de conversi贸n tal como []int(nil).

package main

import "fmt"

func main() {
  fmt.Printf("%s\t| %s\n", "驴Longitud igual a cero?", "驴slice igual a nil?")
  fmt.Printf("%s\n", "- - - - - - - - - - - - - - - - - - - - - - -")

  var s []int // len(s) == 0, s == nil
  fmt.Printf("%t\t\t\t| %t\n", len(s) == 0, s == nil)

  s = nil // len(s) == 0, s == nil
  fmt.Printf("%t\t\t\t| %t\n", len(s) == 0, s == nil)

  s = []int(nil) // len(s) == 0, s == nil
  fmt.Printf("%t\t\t\t| %t\n", len(s) == 0, s == nil)

  s = []int{} // len(s) == 0, s != nil
  fmt.Printf("%t\t\t\t| %t\n", len(s) == 0, s == nil)
}
// Resultado de este programa:
/*
驴Longitud igual a cero?	| 驴slice igual a nil?
- - - - - - - - - - - - - - - - - - - - - - -
true					| true
true					| true
true					| true
true					| false
*/

Si necesitas probar si un slice esta vac铆o, usa la expresi贸n len(s) == 0, en lugar de s == nil.

Funci贸n make

La funci贸n pre construida make crea un slice del tipo de elemento, longitud y capacidad especificado. El argumento de la capacidad puede ser omitido, y en ese caso el valor de la capacidad es igual a la longitud.

make([]T, len)
make([]T, len, cap)

Funci贸n append

La funci贸n pre construida append nos permite a帽adir elementos a un slice.

package main

import "fmt"

func main() {
  var runes []rune
  fmt.Println(runes) // []

  for _, r := range "Hi gophers!" {
    runes = append(runes, r)
  }

  fmt.Printf("%q\n", runes) // ['H' 'i' ' ' 'g' 'o' 'p' 'h' 'e' 'r' 's' '!']
}

La funci贸n append es crucial para entender el funcionamiento de los slices, veamos m谩s de cerca como funciona.

package main

import "fmt"

func main() {
  var x, y []int
  for i := 0; i < 10; i++ {
    y = appendInt(x, i)
    fmt.Printf("%d  cap=%d\t%v\n", i, cap(y), y)
    x = y
  }
}

func appendInt(x []int, y int) []int {
  var z []int
  zlen := len(x) + 1
  if zlen <= cap(x) {
    // Hay espacio para crecer. Extiende el slice.
    z = x[:zlen]
  } else {
    // No hat suficiente espacio. Asigna un nuevo arreglo.
    // Duplica el arreglo, para amortizar la complejidad linear.
    zcap := zlen
    if zcap < 2*len(x) {
      zcap = 2 * len(x)
    }
    z = make([]int, zlen, zcap)
    copy(z, x) // realiza una copia del slice.
  }
  z[len(x)] = y
  return z
}

/*
// Resultado del programa:
0  cap=1	[0]
1  cap=2	[0 1]
2  cap=4	[0 1 2]
3  cap=4	[0 1 2 3]
4  cap=8	[0 1 2 3 4]
5  cap=8	[0 1 2 3 4 5]
6  cap=8	[0 1 2 3 4 5 6]
7  cap=8	[0 1 2 3 4 5 6 7]
8  cap=16	[0 1 2 3 4 5 6 7 8]
9  cap=16	[0 1 2 3 4 5 6 7 8 9]
*/

En la iteraci贸n i=0, el slice x contiene tres elementos [0, 1, 2] pero tiene capacidad 4, entonces hay un 煤nico elemento suelto al final, y appendInt de el elemento 3 puede proceder sin re-asignaci贸n (de memoria). El slice resultante y tiene longitud y capacidad 4, y tiene el mismo arreglo subyacente que el slice original x.

Conclusi贸n

En pr贸ximos posts aprenderemos sobre maps en Go. Hasta la pr贸xima 馃憢馃徑.