Introducción
En Go, las arrays y las slices son estructuras de datos que consisten en una secuencia ordenada de elementos. Estas colecciones de datos son grandes para utilizar cuando desea trabajar con muchos valores relacionados. Le permiten mantener los datos juntos que pertenecen juntos, condensar su código y realizar los mismos métodos y operaciones en varios valores al mismo tiempo.
Aunque las arrays y las slices en Go son ambas secuencias ordenadas de elementos, hay diferencias significativas entre las dos. Una array en Go es una estructura de datos que consiste en una secuencia ordenada de elementos que tiene su capacidad definida en el momento de la creación. Una vez que una array ha asignado su tamaño, el tamaño ya no puede cambiar. Por otro lado, una slice es una versión de tamaño variable de una array, ofreciendo más flexibilidad para los desarrolladores que utilizan estas estructuras de datos. Las slices constituyen lo que se podría considerar arrays en otros lenguajes.
Dada estas diferencias, hay situaciones específicas en las que elegiría una u otra. Si es nuevo en Go, determinando cuándo utilizarlas puede ser confuso: Aunque la versatilidad de las slices las hace una opción más apropiada en la mayoría de las situaciones, hay instancias específicas en las que las arrays pueden optimizar el rendimiento de su programa.
Este artículo cubrirá detalladamente los arreglos y las rebanadas, lo que le proporcionará la información necesaria para elegir adecuadamente entre estos tipos de datos. Además, revisará las maneras más comunes para declarar y trabajar con ambos arreglos y rebanadas. El tutorial primero proporcionará una descripción de los arreglos y cómo manipularlos, seguido de una explicación de las rebanadas y cómo difieren de los mismos.
Arreglos
Los arreglos son estructuras de datos de colección con un número fijo de elementos. Debido a que el tamaño del arreglo es estático, la estructura de datos solo necesita asignar memoria una vez, en comparación con una estructura de datos de longitud variable que debe asignar memoria de manera dinámica para que pueda aumentar o disminuir en el futuro. Aunque la longitud fija de los arreglos pueda hacer que sea algo rígido trabajar con ellos, la asignación de memoria una sola vez puede aumentar la velocidad y el rendimiento de su programa. Debido a esto, los desarrolladores normalmente utilizan arreglos cuando optimizan programas en casos donde la estructura de datos nunca necesitará un número variable de elementos.
Definiendo un Arreglo
Los arreglos se definen declarando el tamaño del arreglo en corchetes [ ]
, seguido del tipo de datos de los elementos. Un arreglo en Go debe tener todos sus elementos del mismo tipo de datos. Después del tipo de datos, puede declarar los valores individuales de los elementos del arreglo en corchetes { }
.
El siguiente es el esquema general para declarar un arreglo:
Nota: Es importante recordar que cada declaración de un nuevo arreglo crea un tipo distinto. Así, aunque [2]int
y [3]int
ambos tienen elementos enteros, su longitud diferente hace que sus tipos de datos sean incompatibles.
Si no declaras los valores de los elementos del arreglo, por defecto son valores cero, lo que significa que los elementos del arreglo están vacíos. Para enteros, esto se representa por 0
, y para cadenas, esto se representa por una cadena vacía.
Por ejemplo, el siguiente arreglo numbers
tiene tres elementos enteros que todavía no tienen un valor:
Si imprimes numbers
, recibirás la siguiente salida:
Output[0 0 0]
Si deseas asignar los valores de los elementos cuando crees el arreglo, coloca los valores en corchetes. Un arreglo de cadenas con valores fijos se parece a esto:
Puedes almacenar un arreglo en una variable y imprimirlo:
Ejecutar un programa con las líneas anteriores te daría la siguiente salida:
Output[blue coral staghorn coral pillar coral elkhorn coral]
Observe que no hay delimitación entre los elementos del arreglo cuando se imprime, lo que hace difícil saber dónde termina un elemento y comienza el siguiente. Debido a esto, a veces resulta útil utilizar la función fmt.Printf
en su lugar, que puede formatear las cadenas antes de imprimirlas en la pantalla. Proporcione el verbo %q
con este comando para instruir a la función para poner comillas alrededor de los valores:
Esto resultará en lo siguiente:
Output["blue coral" "staghorn coral" "pillar coral" "elkhorn coral"]
Ahora cada elemento está citado. El verbo \n
instruye al formateador para agregar un retorno de línea al final.
Con una idea general de cómo declarar arreglos y de qué consisten, ahora puede pasar a aprender cómo especificar elementos de un arreglo con un número de índice.
Índices de Arreglos (y slices)
Cada elemento de un arreglo (y también de un slice) puede ser llamado individualmente a través de índices. Cada elemento corresponde a un número de índice, que es un valor de int
que comienza desde el número de índice 0
y cuenta hacia arriba.
Usaremos un arreglo en los siguientes ejemplos, pero también podría usar un slice, ya que son idénticos en cómo se índica ambos.
Para el arreglo coral
, el desglose de índices se parece a esto:
“blue coral” | “staghorn coral” | “pillar coral” | “elkhorn coral” |
---|---|---|---|
0 | 1 | 2 | 3 |
La primera entidad es la cadena "blue coral"
, que comienza en el índice 0
y la sección termina en el índice 3
con el elemento "elkhorn coral"
.
Porque cada elemento de una sección o arreglo tiene un número de índice correspondiente, podemos acceder y manipularlos de las mismas maneras que con otros tipos de datos secuenciales.
Ahora podemos llamar a un elemento discreto de la sección mediante referencia a su índice numérico:
Outputstaghorn coral
Los números de índice para esta sección van desde 0-3
, como se muestra en la tabla anterior. Por lo tanto, para llamar cualquier elemento individualmente, tendrías que referirte a estos números de índice de esta manera:
Si llamamos al arreglo coral
con un índice mayor que 3
, será fuera de rango porque no será válido:
Outputpanic: runtime error: index out of range
Cuando se indexa un arreglo o sección, siempre debes usar un número positivo. En contraste con algunos lenguajes que te permiten indexar hacia atrás con un número negativo, hacer eso en Go resultará en un error:
Outputinvalid array index -1 (index must be non-negative)
Podemos concatenar los elementos de cadenas dentro de un arreglo o sección con otras cadenas utilizando el operador +
:
OutputSammy loves blue coral
Hemos podido concatenar el elemento de cadena en el índice número 0
con la cadena "Sammy loves "
.
Con números de índice que corresponden a elementos dentro de un arreglo o sección, podemos acceder a cada elemento discretamente y trabajar con esos elementos. Para demostrar esto, próximamente veremos cómo modificar un elemento en cierto índice.
Modificación de Elementos
Podemos usar indexación para cambiar los elementos dentro de un arreglo o slice asignando un elemento con número de índice a un valor diferente. Esto nos da un mayor control sobre los datos en nuestros slices y arreglos, y nos permitirá manipular elementos individuales de forma programática.
Si queremos cambiar el valor de la cadena del elemento en la posición de índice 1
del arreglo coral
de "staghorn coral"
a "foliose coral"
, podemos hacerlo así:
Ahora cuando imprimamos coral
, el arreglo será diferente:
Output["blue coral" "foliose coral" "pillar coral" "elkhorn coral"]
Ahora que sabemos cómo manipular elementos individuales de un arreglo o slice, vamos a ver un par de funciones que nos darán más flexibilidad al trabajar con tipos de datos de colección.
Contar Elementos con len()
En Go, len()
es una función integrada diseñada para ayudarnos a trabajar con arrays y slices. Al igual que con las cadenas, puedes calcular la longitud de un arreglo o slice usando len()
y pasando el arreglo o slice como un parámetro.
Por ejemplo, para saber cuántos elementos hay en el arreglo coral
, usarías:
Si imprimis la longitud para el arreglo coral
, recibirás la siguiente salida:
Output4
Esto proporciona la longitud del arreglo 4
en el tipo de datos int
, lo cual es correcto porque el arreglo coral
tiene cuatro elementos:
Si creas un arreglo de enteros con más elementos, también puedes usar la función len()
para esto:
Esto resultará en la siguiente salida:
Output13
Aunque los ejemplos anteriores tengen relativamente pocos elementos, la función len()
es especialmente útil cuando se desea determinar cuántos elementos hay en un arreglo muy grande.
Próximamente, cubriré cómo agregar un elemento a un tipo de colección de datos y demostraré cómo, debido al tamaño fijo de los arreglos, agregar elementos al final de estos tipos de datos estáticos resultará en un error.
Agregando Elementos con append()
append()
es un método incorporado en Go que agrega elementos a una colección de datos. Sin embargo, este método no funcionará cuando se utilice con un arreglo. Como se mencionó antes, la principal diferencia entre los arreglos y las slices es que el tamaño de un arreglo no se puede modificar después de que ha sido definido. Esto significa que, aunque puedas cambiar los valores de elementos en un arreglo, no puedes hacer que el arreglo sea más grande o menor después de que se haya definido.
Consideramos tu arreglo coral
:
Dígame que quieres agregar el elemento "black coral"
a este array. Si intenta usar la función append()
con el array escribiendo:
Se obtendrá un error en su salida:
Outputfirst argument to append must be slice; have [4]string
Para corregir esto, vamos a aprender más sobre el tipo de dato slice, cómo definir un slice y cómo convertir de un array a un slice.
Slice
Un slice es un tipo de dato en Go que es una secuencia ordenada mutable o cambiable de elementos. Debido a que el tamaño de un slice es variable, ofrece mucha más flexibilidad al usarlos; cuando se trabaja con colecciones de datos que pueden necesitar expandirse o contraerse en el futuro, utilizar un slice asegurará que su código no se encuentre con errores al manipular la longitud de la colección. En la mayoría de los casos, esta mutabilidad es una ventaja adquirida con la posible reasignación de memoria que a veces requieren los slices en comparación con los arrays. Cuando necesita almacenar muchos elementos o iterar sobre elementos y desea ser capaz de modificarlos fácilmente, es probable que desee trabajar con el tipo de dato slice.
Definición de un Slice
Las secciones son definidas declarando el tipo de datos precedido por un conjunto vacío de corchetes angulares ([]
) y una lista de elementos entre paréntesis ({}
). Observarás que, en contraste con los arreglos que requieren un int
entre las comillas para declarar una longitud específica, una sección no tiene nada entre las comillas, representando su longitud variable.
Vamos a crear una sección que contenga elementos del tipo de datos cadena:
Al imprimir la sección, podrás ver los elementos que están en la sección:
Esto resultará en lo siguiente:
Output["shark" "cuttlefish" "squid" "mantis shrimp" "anemone"]
Si quieras crear una sección de cierta longitud sin rellenar los elementos de la colección todavía, puedes usar la función predefinida make()
:
Si la imprimieras ahora, obtendrías:
Output["" "" ""]
Si deseas pre-alocar la memoria para una capacidad determinada, puedes pasar un tercer argumento al make()
:
Esto haría una sección ceroizada con una longitud de 3
y una capacidad pre-alocada de 5
elementos.
Ahora sabes cómo declarar una sección. Sin embargo, esto aún no resuelve el error que teníamos con el arreglo coral
anteriormente. Para usar la función append()
con coral
, primero tendrás que aprender cómo cortar secciones de un arreglo.
Cortar Arrays en Slices
Usando números de índice para determinar los puntos iniciales y finales, puedes llamar a una subsección de valores dentro de un arreglo. Esto se conoce como slicing el arreglo, y lo puedes hacer creando un rango de índices separados por una coma, en la forma de [primero_índice:segundo_índice]
. Es importante recordar sin embargo, que al cortar un arreglo, el resultado es una parte, no un arreglo.
Supongamos que quieras imprimir solo los elementos centrales del arreglo coral
, sin incluir el primer y último elemento. Puedes hacer esto creando una selección comenzando en índice 1
y terminando justo antes de índice 3
:
Ejecutando un programa con esta línea proporcionaría el siguiente resultado:
Output[foliose coral pillar coral]
Al crear una selección, como en [1:3]
, el primer número es donde deseas empezar (inclusivo), y el segundo número es la suma del primero número y el total de elementos que deseas recuperar:
array[starting_index : (starting_index + length_of_slice)]
En este caso, has llamado al segundo elemento (o índice 1) como punto de inicio y llamado dos elementos en total. Así es cómo funciona la calculación:
array[1 : (1 + 2)]
Que es cómo llegaste a esta notación:
Si quieres establecer el inicio o final del arreglo como punto de inicio o final de la selección, puedes omitar uno de los números en la sintaxis array[primero_índice:segundo_índice]
. Por ejemplo, si quieras imprimir los tres primeros elementos del arreglo coral
— que serían "blue coral"
, "foliose coral"
, y "pillar coral"
— podrías escribir:
Esto mostrará:
Output[blue coral foliose coral pillar coral]
Este comentario imprimió el inicio de un arreglo, deteniéndose justo antes del índice 3
.
Para incluir todos los elementos al final de un arreglo, usarías la sintaxis en reversa:
Esto proporcionaría la siguiente sección de corte:
Output[foliose coral pillar coral elkhorn coral]
Esta sección discutió cómo llamar partes individuales de un arreglo mediante la procesión de corte. A continuación, aprenderás cómo usar la procesión de corte para convertir enteros en secciones.
Conversión de un Arreglo a una Sección
Si creas un arreglo y decidas que necesitas que tenga una longitud variable, puedes convertirlo en una sección. Para convertir un arreglo en una sección, utiliza el proceso de corte que aprendiste en la sección Corte de Arreglos de este tutorial, pero esta vez selecciona la sección completa omitiendo ambos números de índice que determinarían los puntos finales:
Recuerda que no puedes convertir la variable coral
en una sección porque, una vez definida en Go, su tipo de datos no puede ser cambiado. Para evitar esto, puedes copiar todo el contenido del arreglo a una nueva variable como sección:
Si imprimes coralSlice
, recibirás la siguiente salida:
Output[blue coral foliose coral pillar coral elkhorn coral]
Ahora, intenta agregar el elemento black coral
como en la sección de arreglos, usando append()
con la sección recién convertida:
Esto mostrará la sección con el elemento agregado:
Output["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral"]
Podemos agregar más de un elemento en una sola declaración append()
.
Output["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia"]
Para combinar dos secciones juntas, puedes usar append()
, pero debes expandir la segunda argumento para agregar usando el sintaxis de expansión ...
:
Output["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]
Ahora que has aprendido cómo anadir un elemento a tu slice, vamos a ver cómo lo eliminas.
Eliminando un Elemento de una Sección
A diferencia de otras lenguajes, Go no proporciona funciones predefinidas para eliminar un elemento de un slice. Los elementos deben ser eliminados mediante la extracción de trozos.
Para eliminar un elemento, debes cortar los elementos antes de ese elemento y después de ese elemento, luego concatenar estos dos nuevos trozos juntos sin el elemento que quieras eliminar.
Si i
es la posición del elemento a eliminar, entonces el formato de este proceso sería como sigue:
Desde coralSlice
, supongamos que se quiera eliminar el elemento "elk horn coral"
. Este elemento está ubicado en la posición de índice 3
.
Output["blue coral" "foliose coral" "pillar coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]
Ahora el elemento en la posición de índice 3
, la cadena "elk horn coral"
, ya no está en nuestro slice coralSlice
.
Podemos también eliminar una sección con el mismo enfoque. Supongamos que no solo quieran eliminar el elemento "elkhorn coral"
, sino también "black coral"
y "antipathes"
. Puede usar una expresión de rango para lograr esto:
Esta expresión eliminara los índices 3
, 4
, y 5
de la muestra:
Output["blue coral" "foliose coral" "pillar coral" "leptopsammia" "massive coral" "soft coral"]
Ahora que sabes cómo agregar y eliminar elementos de una muestra, vamos a ver cómo medir la cantidad de datos que puede contener una muestra en cualquier momento.
Mediando la Capacidad de una Muestra con cap()
Porque las muestras tienen un tamaño variable, el método len()
no es la mejor opción para determinar el tamaño de este tipo de dato. En cambio, puede utilizar la función cap()
para obtener la capacidad de una muestra. Esto mostrará cuántos elementos puede almacenar una muestra, lo cual está determinado por la cantidad de memoria asignada ya para la muestra.
Nota: Porque la longitud y la capacidad de un arreglo son siempre iguales, la función cap()
no funcionará en arreglos.
Un uso común de cap()
es crear una muestra con un número predeterminado de elementos y luego llenar esos elementos programáticamente. Esto evita potenciales asignaciones innecesarias de memoria que podrían ocurrir al usar append()
para agregar elementos más allá de la capacidad actualmente asignada.
Vamos a analizar el escenario en que queremos hacer una lista de números, 0
hasta 3
. Podemos usar append()
en un bucle para hacerlo así, o podemos pre-alocar la sección primero y usar cap()
para iterar para llenar los valores.
Primero, vamos a examinar el uso de append()
:
Output[0 1 2 3]
En este ejemplo, creamos una sección y luego creó un bucle for
que iteraría cuatro veces. Cada vez que se ejecuta la variable de la iteración actual i
se agrega al índice de la sección numbers
. Sin embargo, esto podría llevar a asignaciones de memoria innecesarias que podrían ralentizar tu programa. Cuando se agregan a una sección vacía, cada vez que hagas una llamada a append, el programa verificará la capacidad de la sección. Si el elemento añadido hace que la sección exceda esta capacidad, el programa asignará memoria adicional para contenerlo. Esto crea sobrecarga adicional en tu programa y puede resultar en una ejecución más lento.
Ahora vamos a poblárla sin usar append()
mediante la pre-alocación de cierta longitud/capacidad:
Output[0 1 2 3]
En este ejemplo, utilizamos make()
para crear una sección y le asignó una capacidad previa de 4
elementos. Luego usé la función cap()
en el bucle para iterar por cada elemento inicializado a cero, llenando cada uno hasta que llegara a la capacidad pre-alocada de la sección números
. En cada bucle, coloco el valor actual de la variable de la iteración i
en el índice de la sección numbers
.
Mientras que las funciones append()
y cap()
son funcionalmente equivalentes, el ejemplo de cap()
evita cualquier asignación de memoria adicional que hubiera sido necesaria usando la función append()
.
Crear Slices multidimensionales
También puede definir cortes que consistan en otras secciones, con cada lista entre paréntesis incluida dentro de los paréntesis más grandes de la sección padre. Las colecciones de secciones como estas se llaman secciones multidimensionales. Estos se pueden considerar como dibujando coordenadas multidimensionales; por ejemplo, una colección de cinco secciones cada una de seis elementos podría representar un gráfico bidimensional con una longitud horizontal de cinco y una altura vertical de seis.
Vemos el siguiente ejemplo de una sección multidimensional:
Para acceder a un elemento dentro de esta sección, tendremos que usar múltiples índices, uno para cada dimensión del constructor:
En el código anterior, primero identificamos el elemento en el índice 0
de la sección en el índice 1
, luego indicamos el elemento en el índice 0
de la sección en el índice 0
. Esto producirá lo siguiente:
OutputSammy
shark
Los valores de índice para el resto de los elementos individuales son:
Cuando trabajas con trozos multidimensionales, es importante tener en cuenta que necesitarás referirte a más de un índice numérico para acceder a elementos específicos dentro del trozo de nested slice correspondiente.
Conclusión
En este tutorial, aprendiste los fundamentos de trabajo con arrays y trozos en Go. Llegaste a ejercicios variados para demostrar cómo las diferencias entre estructuras de datos fijas y variables afectan el uso adecuado de estos estructuras de datos.
Para continuar estudiando estructuras de datos en Go, consulta nuestra guía sobre Mapas en Go, o explora la serie completa Cómo programar en Go.
Source:
https://www.digitalocean.com/community/tutorials/understanding-arrays-and-slices-in-go