martes, 6 de noviembre de 2018

Slices en Go

Un slice ("rebanada" en español), viene a ser un segmento o una porción de un array. En la práctica, un slice nos ofrece nuevas posibilidades para tratar con colecciones de datos.

Asignando rebanadas

En el siguiente ejemplo vamos a crear un array con una serie de elementos, y, a continuación, vamos a extraer una porción o rebanada de este array y se la asignaremos a un slice, el cual crearemos por inferencia.

package main

import "fmt"

func main() {
   // Creacion de un slice por inferencia
   // a partir de un array
   miArray := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
   miSlice := miArray[3:7]

   fmt.Printf("miArray: %T %v - len:%v - cap:%v\n",
      miArray, miArray, len(miArray), cap(miArray))

   fmt.Printf("miSlice: %T %v - len:%v - cap:%v\n",
      miSlice, miSlice, len(miSlice), cap(miSlice))
}

Aparentemente, el slice podría parecer un array normal con la copia del fragmento extraido del array. Pero cuando ejecutamos el código, veremos algunas diferencias:

$ go run slices.go
miArray: [10]int [0 1 2 3 4 5 6 7 8 9] - len:10 - cap:10
miSlice: []int [3 4 5 6] - len:4 - cap:7
  • El slice no tiene un tamaño definido ([]int)
  • La capacidad del slice es mayor que la longitud del mismo: se han asignado cuatro elementos (longitud), pero su capacidad es de 7.

Esto significa que el slice puede redimensionarse dinámicamente, es decir, que puede variar de tamaño. Esto supone una ventaja muy grande con respecto a los arrays, que son estáticos y no pueden variar de tamaño.

Creación de un slice

Un slice se puede crear de varias maneras:

// Creacion de un slice por inferencia
// a partir de un array
miArray := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
miSlice := miArray[3:7]

// Declaracion de un slice y posterior asignacion
// Tendra longitud 3 y capacidad 3
var miSlice2 []int
miSlice2 = []int{1, 2, 3}

// *** FORMA RECOMENDADA ***
// Creacion de un slice con longitud 3 y capacidad 3
miSlice3 := make([]byte, 3)

// Creacion de un slice con longitud 3 y capacidad 6
miSlice4 := make([]float64, 3, 6)

A la hora de crear un slice es recomendable utilizar la función make(), ya que aporta una mejor legibilidad en el código, y porque permite controlar tanto su tamaño como su capacidad.

Asignación de datos a un slice

Existen varias maneras de asignar datos a un slice:

// Extrayendo una porcion a partir de un array
miSlice = miArray[3:7]

// Asignando un slice ad-hoc
miSlice2 = []int{1, 2, 3}

Agregando nuevos valores al slice

Un slice puede crecer dinámicamente, agregando valores según sean necesarios. Esto se consigue mediante la función append()

miSlice5 := []float64{12.2, 15.5, 18.9}

// Agregacion de nuevos valores
miSlice5 = append(miSlice5, 21.4, 23.7, 7.5)

// Ahora miSlice5 contiene [12.2 15.5 18.9 21.4 23.7 7.5]

Copiando valores a un slice

Existen diferentes maneras para copiar valores a un slice:

// Copia los elementos de un array
miSlice := miArray[:]

// Copia entre slices, mediante asignacion
miSlice2 = miSlice

// Metodo recomendado para copiar entre slices
miSlice5 := []float64{12.2, 15.5, 18.9}
miSlice7 := make([]float64, 3)
copy(miSlice7, miSlice5)

La función copy() es la forma recomendada para copiar todos los valores de un slice a otro.

Nota importante: Los datos a copiar deben ser del mismo tipo que el tipo del slice

Eliminando elementos de un slice

miSlice8 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

// Eliminar los dos primeros elementos
// 3, 4, 5, 6, 7, 8, 9, 10
miSlice8 = miSlice8[2:]

// Eliminar los dos ultimos elementos
// 3, 4, 5, 6, 7, 8
miSlice8 = miSlice8[:6]

Unión de varios slices

La sintaxis de la función append() es la siguiente:

append(slice, valor1, valor2, valor_n)

Sin intentamos unir dos slices dará un error, pues a partir del segundo parámetro espera un valor del mismo tipo que el slice, no un tipo slice. Es decir, espera una lista de elementos y no un slice.

Para añadir un slice mediante la función append(), debemos utilizar la siguiente sintaxis:

append(slice1, slice2...)

Los puntos suspensivos después del nombre del slice, indican a Go que descomponga dicho slice por la lista de elementos que contiene.

Veamos un ejemplo:

// Adicion de varios slices
slice1 := []string{"Rafael", "Eduardo"}
slice2 := []string{"Cristina", "Antonio"}
slice3 := []string{"Lucía", "José", "Víctor", "Ana"}
slice3 = append(slice3, slice1...)
slice3 = append(slice3, slice2...)
fmt.Println("slice3: ", slice3)

El resultado será el siguiente:

slice3:  [Lucía José Víctor Ana Rafael Eduardo Cristina Antonio]

Slices como referencias a un array

Cuando trabajamos con slices, en realidad estamos trabajando con una referencia a un array. Por tanto, el slice esta apuntando a la dirección de memoria del array que contiene los datos, y, por tanto, los cambios en algún elemento afectarán a ambos.

En el siguiente ejemplo se crea un array con una lista de nombres. A continuación, se crean dos slices, cada uno de los cuales referencia a una porción del array. Después, se cambia el elemento "Cristina" por "Nerea" en el segundo slice. Al ser una referencia, en realidad se está modificando en el array, por lo que el elemento cambia tanto para el array como para los slices.

arr := [5]string{"Rafael", "Edu", "Cristina", "Antonio", "Lucía"}

// ["Rafael", "Edu", "Nerea"]
sl1 := arr[:3]

// ["Nerea", "Antonio", "Lucía"]
sl2 := arr[2:]

fmt.Printf("arr: %v\n", arr)
fmt.Printf("sl1: %v\n", sl1)
fmt.Printf("sl2: %v\n", sl2)

// Cambiar "Cristina" por "Nerea"
sl2[0] = "Nerea"

fmt.Printf("arr: %v\n", arr)
fmt.Printf("sl1: %v\n", sl1)
fmt.Printf("sl2: %v\n", sl2)

El resultado será el siguiente:

arr: [Rafael Edu Cristina Antonio Lucía]
sl1: [Rafael Edu Cristina]
sl2: [Cristina Antonio Lucía]
arr: [Rafael Edu Nerea Antonio Lucía]
sl1: [Rafael Edu Nerea]
sl2: [Nerea Antonio Lucía]

Enlaces de interés

No hay comentarios:

Publicar un comentario