Mostrando entradas con la etiqueta Básico. Mostrar todas las entradas
Mostrando entradas con la etiqueta Básico. Mostrar todas las entradas

martes, 4 de diciembre de 2018

Comandos del compilador de Go

Compilador de Go

Una vez instalado el paquete de Go en tu máquina, es hora de conocer el compilador y los útiles comandos que nos proporciona. Para contar con estas ventajas hemos de abrir la terminal o consola que tengas en tu sistema operativo.

Sintaxis

Puedes invocar al compilador de Go mediante go. Su sintaxis es la siguiente:

$ go <comando> [argumentos]

Versión del compilador

$ go version
go version go1.11.1 windows/amd64

Nota: La información mostrada se corresponderá con la versión que hayas instalado.

Ayuda del compilador

Para obtener ayuda sobre los posibles comandos del compilador de Go:

$ go help

Para obtener ayuda detallada sobre un comando específico del compilador de Go:

$ go help <comando>

Por ejemplo:

$ go help version
usage: go version

Version prints the Go version, as reported by runtime.Version.

Información del entorno de Go

Para obtener obtener los valores del entorno de Go en nuestra máquina:

$ go env

Mostrará las variables de entorno y sus valores, incluyendo GOPATH, la cual apunta al directorio o carpeta donde está instalado el compilador de Go.

Compilación de un programa

Un compilador lee el código de nuestra aplicación, chequea posibles errores, resuelve dependencias (paquetes y librerías) y, si todo es correcto, transforma el código Go en código máquina, generando un código binario que entiende el ordenador y puede ejecutar.

Para compilar usamos el siguiente comando:

$ go build <[path/]archivo.go>

El resultado será un archivo ejecutable.

Ejecución de un programa

En el anterior comando (go build), compilamos el programa, obteniendo el archivo ejecutable, el cual, después, hemos de ejecutar para correr el programa.

Podemos automatizar este proceso mediante:

$ go run <[path/]archivo.go>

Este comando compila el programa, obteniendo un ejecutable en un directorio o carpeta temporal, y, automáticamente, lanza dicho programa.

Reformateo del código

El compìlador de Go posee un comando muy interesante, el cual permite reformatear el código. Esto permite, de forma automática, organizar el código por llaves, sangrías o tabulaciones, líneas en blanco, etc... de forma elegante.

Para reformatear un archivo de código:

$ go fmt <[path/]archivo.go>

Si deseamos reformatear el código de uno o varios paquetes:

$ go fmt [paquetes]

Descarga e instalación de paquetes

Cuando desarrollamos aplicaciones podemos utilizar paquetes de terceros. Para llevar a cabo esto, podemos instalar e instalar dichos paquetes desde un repositorio en internet, normalmente de Git. Esto se consigue con el siguiente comando:

$ go get [paquetes]

Puede ocurrir que un paquete pueda, a su vez, utilizar otros paquetes, por lo que tiene una dependencia. El comando anterior, también resuelve estas dependencias y las instala.

Los paquetes se instalan en el directorio pkg, dentro de la ruta donde apunte la variable de entorno GOPATH.

Actualización de paquetes

Los paquetes en Go, al igual que en otros lenguajes de programación, van evolucionando. Aparecen nuevas funcionalidades, se corrigen errores, se optimizan...

Para actualizar un paquete ya instalado a la última versión disponible:

$ go doc fix [paquetes]

Testeo de paquetes

Para probar paquetes y comprobar su estado, utilizaremos el siguiente comando:

$ go test [paquetes]

Listado de paquetes

Para visualizar la lista de paquetes de paquetes instalados

$ go list

Documentación de paquetes

Un paquete en Go es un conjunto de librerías de funciones o utilidades que extienden las posibilidades del lenguaje. El propio lenguaje Go ofrece un conjunto de paquetes estándar que conforman el lenguaje, pero también podemos crear nuestros propios paquetes y usar paquetes de terceros.

Los paquetes se suelen documentar, y podemos acceder a su documentacion mediante el siguiente comando:

$ go doc <paquete>

Por ejemplo:

$ go doc fmt

Un paquete puede contener múltiples funciones. Si queremos obtener documentación específica de una determinada función, podemos especificar el nombre del paquete, seguido de un punto y del nombre de la función. En el siguiente ejemplo obtendremos la documentación de la función Println() del paquete fmt:

$ go doc fmt.Println
func Println(a ...interface{}) (n int, err error)
    Println formats using the default formats for its operands and writes to
    standard output. Spaces are always added between operands and a newline is
    appended. It returns the number of bytes written and any write error
    encountered.

Enlaces de interés

domingo, 25 de noviembre de 2018

Rangos en Go

De forma natural, una colección de datos se recorre mediante un bucle, desde el primer elemento hasta el último. Para conseguir esto, normalmente utilizamos la función len() para obtener el tamaño o longitud de la colección, que nos sirve como límite en dicho bucle.

package main

import "fmt"

func main() {
   var nombres = [5]string{"Rafael", "Eduardo", 
      "Nerea", "Cintia", "Clemen"}

   for i := 0; i < len(nombres); i++ {
      fmt.Printf("Indice: %d, Nombre: %s\n", i, nombres[i])
   }
}

Este método de recorrer una colección se puede simplificar mucho mediante rangos. Un rango permite iterar o recorrer, de forma automática y secuencial, desde el inicio hasta el fin, una colección de datos.

Si usamos un rango sobre el array quedaría de la siguiente manera:

for indice, valor := range nombres {
   fmt.Printf("Indice: %d, Nombre: %s\n", indice, valor)
}

El rango retorna siempre dos resultados en cada iteración: el índice (o posición) y el valor del elemento actual en curso.

Si no nos interesa (por ejemplo) el índice, podemos omitirlo mediante el operador underscore (o guión bajo, _):

for _, valor := range nombres {
   fmt.Printf("Nombre: %s\n", valor)
}

Un rango puede trabajar con arrays, slices, strings y mapas. En el caso de los mapas, retornará la clave y el valor de cada elemento del mismo.

   // Definicion del mapa
   centros := make(map[string]string)

   // Adicion de claves y valores
   centros["MADE"] = "Madrid Este"
   centros["MADN"] = "Madrid Norte"
   centros["MOS"] = "Mostoles"
   centros["POZ"] = "Pozuelo"
   centros["BCN"] = "Barcelona"

   // Recorrido del mapa con un rango
   for clave, valor := range centros {
      fmt.Printf("Clave: %s, Nombre: %s\n", clave, valor)
   }

Nota: Los rangos también puede operar con canales, que es un concepto relacionado con la concurrencia.

Enlaces de interés

Funciones recursivas en Go

La recursividad es la habilidad de una función de llamarse a sí misma. Esta característica permite reducir y simplificar operaciones complejas, tales como una navegación jerárquica en un árbol o el cálculo de operaciones matemáticas como el factorial.

package main

import "fmt"

func main() {
   // factorial(5) = 5 * 4 * 3 * 2 * 1 = 120
   fmt.Println("Factorial de 5: ", factorial(5))
}

// Funcion que calcula un factorial
func factorial(n int) int {
   if n == 0 {
      return 1
   }

   return n * factorial(n-1)
}

Hay que tener en cuenta que cada vez que se llama a sí misma se crea un nuevo nivel de ejecución, en el que, hasta que el nivel más profundo no termina, no se irá resolviendo el retorno de las llamadas hacia atrás.

También hay que tener en cuenta que hay que controlar el nivel de profundidad de llamadas, para evitar un colapso en la memoria, al entrar en una recursividad infinita.

En el ejemplo del factorial, se va llamando a sí misma con el cálculo del factorial del número decrementado, hasta llegar a 1, momento en el que deja de llamarse a sí misma, y, por tanto, en ese momento termina la recursividad (y la profundidad de la misma) y comienza el retorno hacia atrás del resultado, disminuyendo nuevamente la profundidad de las llamadas hasta el primer nivel.

El siguiente ejemplo permite calcular la secuencia Fibonacci. Para limitar la recursividad, el factor de la secuencia debe ser mayor de 1.

package main

import "fmt"

func main() {
   // fibonacci(7) = 0 1 2 3 5 8 13
   fmt.Println("fibonacci(7): ", fibonacci(7))
}


// Funcion que calcula la secuencia fibonacci
func fibonacci(i int) int {
   if i <= 1 {
      return i
   }

   return fibonacci(i-1) + fibonacci(i-2)
}

Enlaces de interés

Funciones anónimas y closures en Go

Una función anónima es una función definida internamente dentro de un bloque de código, y que no tiene identificador o nombre. Este tipo de funciones no son reutilizables como paquetes, siendo utilizadas únicamente dentro del bloque de código en el que son declaradas.

El siguiente código declara una función anónima dentro de la función main(), la cual sólo tiene visibilidad dentro de esta función.

package main

import "fmt"

func main() {

   // Ejemplo basico de funcion anonima
   var num int = 0

   incrementar := func() int {
      num++
      return num
   }

   fmt.Println("Num: ", num)  // 0
   fmt.Println("Num: ", incrementar())  // 1
   fmt.Println("Num: ", incrementar())  // 2
}

closure

Un closure es una implementación de una función anónima como retorno de una función principal. Esta técnica es muy útil, ya que permite que la función principal pueda ser asignada o delegada a una "variable" que es en sí el closure. En ese momento, las variables de la función principal se registran o inicializan y el control pasa al closure.

Cuando se invoca al closure, internamente llama a la sentencia return, que es la que tiene la función anónima con el bloque de código de la operativa.

Para ver más claro esto, vamos a implementar el código anterior mediante un closure:

package main

import "fmt"

func main() {

  // Ejemplo basico de closure
   incr := increment()

   for i := 1; i < 10; i++ {
      fmt.Println("incr() > ", incr())
   }

}

// Funcion que retorna una funcion anonima
func increment() func() int {
   var num int = 0

   return func() int {
      num++
      return num
   }
}

También podemos utilizar parámetros en nuestros closures. Para ello, los parámetros se definen en la función de retorno.

package main

import "fmt"

func main() {

   // Ejemplo de closure con parametro
   acumulador := acumular()

   fmt.Println("acumulador() > ", acumulador(4))  // 4
   fmt.Println("acumulador() > ", acumulador(6))  // 10
   fmt.Println("acumulador() > ", acumulador(20)) // 30

}

// Funcion anonima con parametro
func acumular() func(incremento int) int {
   var num int = 0

   return func(incremento int) int {
      num += incremento
      return num
   }
}

El código anterior es muy básico, pero podemos implementar operativas más complejas, como la secuencia Fibonacci:

package main

import "fmt"

func main() {

   // Implementacion de la secuencia Fibonacci
   fmt.Println("Secuencia Fibonacci")

   proximaSecuencia := fibonacci()

   for i := 1; i < 10; i++ {
      fmt.Println(proximaSecuencia())
   }

 }

// Secuencia Fibonacci
func fibonacci() func() int {
   var num1, num2 int = 0, 1

   return func() int {
      var result int = num1 + num2
      num1 = num2
      num2 = result
      return result
   }
}

Enlaces de interés

domingo, 18 de noviembre de 2018

Conversión de datos en Go

Los datos son indispensables en nuestros programas. Gracias a las variables y a las constantes podemos almacenar y consultar datos. Pero nuestros programas también realizarán cálculos y transformaciones sobre los datos.

Conversión entre tipos numéricos

Vamos a probar el siguiente programa, el cual suma dos variables de tipo entero:

package main

import "fmt"

func main() {
   var a byte = 64
   var b int32 = 1567890

   fmt.Printf("%v + %v = %v\n", a, b, a+b)
}

A pesar de que las dos variables son de tipo entero, cada una de ellas es de un tipo específico y distinto. Por este motivo, al ejecutar este programa se producirá el siguiente error:

invalid operation: a + b (mismatched types byte and int32)

Tiene todo su sentido: un tipo byte es un entero que solo puede almacenar valores entre 0 y 255, mientras que el tipo int32 tiene un tamaño de cuatro bytes.

Para poder llevar a cabo esta suma, los dos valores deben ser del mismo tipo. Por tanto, habrá que convertir la variable con el tipo más pequeño al tipo más grande.

   fmt.Printf("%v + %v = %v\n", a, b, int32(a)+b)

Para hacer la conversión de un valor a un tipo concreto, basta con escribir el tipo a convertir, seguido de unos paréntesis, los cuales contendrán el valor a convertir.

tipo(valor)

En el siguiente ejemplo vamos a multiplicar un número de tipo byte con otro número de tipo float32. Primero se convertirá el tipo entero a tipo real y se multiplicarán como números reales. Después, se convertirá el tipo real a tipo entero y se multiplicarán como números enteros:

   var a byte = 64
   var pi float32 = 3.141519

   fmt.Printf("%v x %v = %v\n", a, pi, float32(a)*pi)
   fmt.Printf("%v x %v = %v\n", a, pi, a*byte(pi))

El resultado, obviamente, será distinto:

64 x 3.141519 = 201.05722
64 x 3.141519 = 192

Conversión a string

En muchas ocasiones necesitaremos utilizar un valor numérico o un valor lógico en formato de cadena de texto, o bien concatenar un número a una cadena, por lo que será necesario convertir a formato string.

En estos casos necesitaremos usar el paquete strconv, que contiene multitud de funciones que facilitan las conversiones entre diferentes tipos.

Las siguientes funciones retornan una cadena texto, proveniente de la conversión de un tipo numérico o de un tipo lógico:

strconv.FormatBool(valorBool)
strconv.FormatFloat(valorFloat64, 'base', decimales, tamaño)
strconv.FormatInt(valorInt, tamaño)
strconv.FormatUint(valorUint, tamaño)
strconv.Itoa(valorInt)

El siguiente ejemplo muestra la conversión de un tipo byte y un tipo float32 a formato de cadena de texto:

var a byte = 64
var pi float32 = 3.141519

var mensaje string = "El valor de PI es " +
   strconv.FormatFloat(float64(pi), 'f', 4, 64)

fmt.Println(mensaje)

mensaje = "El valor de a es " +
   strconv.Itoa(int(a))

fmt.Println(mensaje)

Estos métodos son muy efectivos, aunque pueden ser algo confusos y complejos de utilizar. Por este motivo, existe una forma más sencilla de realizar conversiones a cadenas de texto, a través del método Sprintf() del paquete fmt.

El método Sprintf retorna una cadena formateada en base a los parámetros que se faciliten. El primer parámetro es una cadena de texto con la plantilla a formatear. Los siguientes parámetros se corresponden a los valores a ser utilizados dentro de la plantilla.

En la plantilla se especifica texto con caracteres especiales, los cuales serán sustituidos por los valores contenidos en el resto de parámetros, en el mismo orden en que se especifiquen. Estos caracteres especiales permiten indicar el formato en que se mostrará el valor.

He aquí algunos de los caracteres más utilizados:

%v El valor en el formato por defecto
%T El nombre del tipo del valor
%t La palabra true o false
%d Base 10 (formato decimal, del 0 al 9)
%b Base 2 (formato binario, 0 ó 1)
%o Base 8 (formato octal, del 0 al 7)
%e Notación científica, como -1.234456e+78
%E Notación científica, como -1.234456E+78
%f Punto decimal sin exponente, como 123.456
%F Punto decimal sin exponente, como 123.456

Cuando trabajamos con números reales, podemos especificar también el ancho y la precisión del valor:

%f     Ancho y precisión por defecto
%9f    Ancho 9 caracteres, precisión por defecto
%.2f   Ancho por defecto, precisión de 2
%9.2f  Ancho 9 caracteres, precisión de 2
%9.f   Ancho 9 caracteres, precisión de 0

El ejemplo anterior se podría simplificar de esta manera:

mensaje = fmt.Sprintf("El valor de PI es %.4f", pi)
fmt.Println(mensaje)

mensaje = fmt.Sprintf("El valor de a es %d", a)
fmt.Println(mensaje)

Conversión de string a número

Con una cadena de texto no es posible realizar operaciones matemáticas, aunque dicha cadena contenga un número.

var num1, num2 string = "20", "32"
fmt.Println("num1 + num2 = ", num1+num2)

El ejemplo anterior dará como resultado la concatenación de dos cadenas de texto, no la suma de dos números:

num1 + num2 =  2032

Para obtener la suma de los números, será preciso convertir primero las cadenas en números para poder realizar las operaciones matemáticas. Pero es imprescindible que las cadenas contengan números válidos y que la conversión se realice al formato numérico válido correspondiente.

Para realizar estas conversiones, el paquete strconv dispone de varias funciones, dependiendo del tipo de dato a ser convertido:

varBool, err := strconv.ParseBool("false")
varFloat, err := strconv.ParseFloat("3.141519", 64)
varInt, err := strconv.ParseInt("-365", 10, 64)
varInt, err := strconv.Atoi("-365")
varUint, err := strconv.ParseUint("47", 10, 64)

Estas funciones retornan dos valores: el valor convertido al tipo correspondiente y un error, en caso de que éste se produzca.

El siguiente ejemplo convierte los valores de cadena a entero, y después realiza la suma de ambos números:

conv1, err := strconv.Atoi(num1)

if err != nil {
   fmt.Println("Error al convertir num1: ", err)
} else {
   conv2, err := strconv.Atoi(num2)

   if err != nil {
      fmt.Println("Error al convertir num2: ", err)
   } else {
      fmt.Println("conv1 + conv2 = ", conv1+conv2)
   }
}

En cada conversión, verificamos (a través de la sentencia if) si hubo algún error en la conversión. Si la variable err no contiene el valor nil (valor nulo), significa que se ha producido un error, y en ese caso informará del error producido.

Si no hubo error en ninguna de las dos conversiones, procederá a visualizar en pantalla la suma de los números obtenidos de la conversión de las dos cadenas de texto.

Enlaces de interés

miércoles, 14 de noviembre de 2018

Estructuras de control: bucles for

Un bucle permite ejecutar un bloque de código de forma repetida y controlada. Cada ejecución del código del bloque del bucle se denomina iteración.

Un bucle comienza con la sentencia for, y para controlar las iteraciones del bucle utilizamos una variable a modo de iterador, la cual se declara y se inicializa con un valor inicial. Después, se indica una condición que, mientras se cumpla, se ejecutará el bloque de código del bucle. Para que la variable cambie de valor en cada iteración, debe haber una asignación diferente, normalmente un incremento o un decremento.

Después de definir los parámetros de control del bucle, se especifica el código que ejecutará el bucle, encerrado entre llaves.

El siguiente ejemplo visualiza en pantalla una tabla de multiplicar, cuyo multiplicador va del 1 al 10:

  var tabla int = 2

  for i := 1; i <= 10; i++ {
    fmt.Printf("%d x %d = %d\n", tabla, i, tabla*i)
  }

La variable i es el iterador del bucle. El iterador va cambiando de valor en cada iteración, mientras se cumpla la condición. Cuando deje de cumplirse la condición, se saldrá del bucle. En este caso, el iterador comienza con el valor 1. En cada iteración, el iterador se incrementará. Mientras el iterador sea menor o igual que 10, se irá ejecutando. Pero cuando llegue al valor 11, saldrá del bucle.

2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
2 x 4 = 8
2 x 5 = 10
2 x 6 = 12
2 x 7 = 14
2 x 8 = 16
2 x 9 = 18
2 x 10 = 20

El siguiente ejemplo realizará una cuenta atrás, desde 100 hasta 0, de dos en dos:

  for i := 100; i >= 0; i-=2 {
    fmt.Printf("%d, ", i)
  }

También podemos anidar bucles, es decir, definir y ejecutar un bucle dentro de otro. En el siguiente ejemplo, vamos a visualizar las tablas de multiplicar del 1 al 10, cada una de las tablas tendrá un multiplicador del 1 al 10:

  for x := 1; x <= 10; x++ {
    for i := 1; i <= 10; i++ {
      fmt.Printf("%d x %d = %d\n", x, i, x*i)
    }
  }

Bucles for Vs bucles while

Go no contempla bucles while como en otros lenguajes de programación. Sin embargo, es posible utilizar un bucle for de la misma manera a como se utiliza un bucle while

  var tabla int = 2
  var i int = 1

  for i <= 10 {  // while i <= 10
    fmt.Printf("%d x %d = %d\n", tabla, i, tabla*i)
    i++
  }

El iterador (variable i se declara e inicializa antes del bucle. En la declaración del bucle definimos únicamente la condición del bucle. Mientras (while) se cumpla la condición, el bloque de código se repetirá. Por último, dentro del bloque debe haber algo que cambie el valor del iterador, para poder llegar a incumplir la condición y salir del bucle. En este caso, es el incrementador (i++)

Sentencias break y continue

En los ejemplos vistos anteriormente, los bucles siguen una lógica lineal, es decir, que su ciclo de vida está ya definido y se puede predecir. Sin embargo, puede haber ocasiones en las cuales no sabremos cuándo hay que salir del bucle (puede ser en la primera iteración o en la número mil millones), o bien, podemos querer dejar de ejecutar el código del bucle y empezar una nueva iteración.

La sentencia break permite romper la iteración del bucle, saliendo directamente de él.

La sentencia continue deja de ejecutar el código del bucle, saltando directamente al inicio del bucle e iniciando una nueva iteración.

En el siguiente ejemplo vamos a crear un bucle infinito (no hay condición o la condición no se cumple nunca). Dentro de cada iteración va a solicitar al usuario que introduzca una palabra. Si la longitud de la palabra es menor de cinco caracteres se irá al inicio del bucle, omitiendo el resto del código del bucle e iniciando una nueva iteración. Si la longitud es mayor o igual de cinco caracteres, mostrará el mensaje "Tu palabra es: ", y repetirá la palabra introducida. Si la palabra introducida es "ok" (minúsculas) o "OK" (mayúsculas), saldrá del bucle.

  var palabra string

  fmt.Println("Escribe una palabra que la repito")
  fmt.Println("Para salir, escribe 'OK'")

  // Bucle infinito
  for {
    fmt.Print("Dame palabra > ")
    fmt.Scanln(&palabra)

    if palabra == "OK" || palabra == "ok" {
      break   // Sale del bucle
    } else if len(palabra) < 5 {
      continue // Inicio del bucle
    } else {
      fmt.Println("   Tu palabra es: ", palabra)
    }
  }

  fmt.Println("*** Fin del bucle ***")

El resultado es el siguiente:

Escribe una palabra que la repito
Para salir, escribe 'OK'
Dame palabra >  ratón
   Tu palabra es:  ratón
Dame palabra >  hola
Dame palabra >  vaso
Dame palabra >  mano
Dame palabra >  ordenador
   Tu palabra es:  ordenador
Dame palabra >  teléfono
   Tu palabra es:  teléfono
Dame palabra >  ok
*** Fin del bucle ***

Enlaces de interés

Estructuras de control en Go: bloques switch

En determinadas situaciones, será necesario evaluar múltiples condiciones para un mismo dato.

En el siguiente ejemplo muestra las etapas de la vida, en base a la edad:

  var edad int = 47

  if edad >= 0 && edad < 3 {
    fmt.Println("Primera infancia")
  } else if edad >= 3 && edad < 6 {
    fmt.Println("Niñez temprana")
  } else if edad >= 6 && edad < 12 {
    fmt.Println("Niñez intermedia")
  } else if edad >= 12 && edad < 18 {
    fmt.Println("Adolescencia")
  } else if edad >= 18 && edad < 35 {
    fmt.Println("Juventud")
  } else if edad >= 35 && edad < 50 {
    fmt.Println("Madurez")
  } else if edad >= 50 && edad < 65 {
    fmt.Println("Adultez")
  } else if edad >= 65 {
    fmt.Println("Vejez o Tercera Edad")
  }

Este código puede ser algo confuso, debido a tanto if, else y a las llaves de cada bloque de código

El siguiente código adapta los bloques if y else de antes, a un formato más legible y más sencillo de entender:

  switch {
    case edad >= 0 && edad < 3:
      fmt.Println("Primera infancia")
    case edad >= 3 && edad < 6:
      fmt.Println("Niñez temprana")
    case edad >= 6 && edad < 12:
      fmt.Println("Niñez intermedia")
    case edad >= 12 && edad < 18:
      fmt.Println("Adolescencia")
    case edad >= 18 && edad < 35:
      fmt.Println("Juventud")
    case edad >= 35 && edad < 50:
      fmt.Println("Madurez")
    case edad >= 50 && edad < 65:
      fmt.Println("Adultez")
    case edad >= 65:
      fmt.Println("Vejez o Tercera Edad")
  }

Go ofrece un tipo de bloque condicional, llamado switch, el cual permite evaluar diferentes casos (o condiciones), mediante la sentencia case. Cada caso evalúa una condición y, en el caso de ser cierta (true), abrirá un bloque de código que se ejecutará en tal caso. Dicho bloque no requiere el uso de las llaves.

Una de las ventajas del bloque switch es que podemos especificar al principio la variable a evaluar, y en cada case solamente indicamos el valor a evaluar.

El siguiente ejemplo evalúa los posibles valores de la variable region, a fin de poder determinar de qué región se trata.

  var region string = "EU"

  switch region {
    case "CH":   // region == "CH"
      fmt.Println("China")
    case "LAT":
      fmt.Println("Latino América")
    case "RU":
      fmt.Println("Rusia")
    case "US":
      fmt.Println("Estados Unidos")
    default:
      fmt.Println("Europa")
  }

En este tipo de evaluación, la condición será siempre de igualdad, es decir, que cada caso evaluará un valor concreto (variable == valor).

La cláusula default permite definir un caso por defecto, es decir, un caso que aplica cuando el valor de la variable no se corresponde con ninguno de los anteriores case.

Para evaluar varios valores en un mismo case, éstos se especifican separados por comas.

  var diaSemana int = 8

  switch diaSemana {
    case 1, 2, 3, 4, 5:
      fmt.Println("Es día laboral")
    case 6, 7:
      fmt.Println("Es fin de semana")
    default:
      fmt.Printf("Día %d no es válido", diaSemana)
  }

Enlaces de interés

Estructuras de control en Go: Bloques if y else

Una estructura de control permite la ejecución controlada de código (acciones) acordes a diferentes situaciones (o condiciones), las cuales son evaluadas por expresiones de comparación y/o expresiones lógicas o relacionales.

Un bloque es, básicamente, un conjunto de sentencias de código. A través de la sentencia if podemos definir un bloque de código que sólo se ejecutará en el caso de que se cumpla una determinada condición. Esto nos ayudará a crear programas más inteligentes, comportándose de formas distintas y controladas. Para ello, se evalúa una condición lógica, que es una comparación de datos, lo que dará un resultado de tipo bool. Si el resultado es true se ejecutará el código que se encuentra dentro del bloque, el cual está definido entre llaves ({ y }).

El siguiente ejemplo, evalúa la variable edad. Si el valor de la variable es mayor o igual a 18, significa que es mayor de edad.

var edad int = 18

if edad >= 18 {
   fmt.Println("Eres mayor de edad")
}

Si queremos realizar otra operación, en caso contrario, podemos añadir la cláusula else a la sentencia if.

var edad int = 18

if edad >= 18 {
   fmt.Println("Eres mayor de edad")
} else {
   fmt.Println("Eres menor de edad")
}

Podemos utilizar los operadores relaciones para evaluar más de una condición. En el siguiente ejemplo, evaluamos si la edad está entre 16 y 67 años, lo que significa que está en edad de trabajar:

if edad >=16 && edad <= 67 {
   fmt.Println("Estás en edad de trabajar")
}

Para evaluar condiciones más complejas o más específicas, se pueden enlazar más sentencias if en cláusulas else. En el siguiente ejemplo evaluamos tres posibles casos sobre la edad: ser aún demasiado joven para trabajar, estar en edad de trabajar o estar jubilado.

edad = 68

if edad >=16 && edad <= 67 {
  fmt.Println("Estás en edad de trabajar")
} else if edad < 16 {
  fmt.Println("Eres aún muy joven para trabajar")
} else {
  fmt.Println("Estás ya jubilado")
}

Enlaces de interés

lunes, 12 de noviembre de 2018

Mapas en Go

Un mapa es un tipo de colección dinámico que está referenciado por una clave, en lugar de por un índice. Su concepto es muy similar al de un diccionario en otros lenguajes de programación.

Hay dos formas de declarar y crear un mapa en Go:

var variableMapa map[tipoClave]tipoValor

variableMapa := make(map[tipoClave]tipoValor

Para añadir o modificar elementos del mapa, basta con indicar la clave y el valor para dicha clave. Si la clave no existe en el mapa, la añade; si ya existe, le cambia el valor.

variableMapa[clave] = valor

Para eliminar un elemento del mapa, se utiliza la función delete():

delete(variableMapa, clave)

Para saber si existe un elemento en el mapa, accederemos a dicho elemento, el cual nos devolverá dos valores: la propia clave y un valor boolean con el valor true si existe, o false si no existe:

var1, var2 = variableMapa[clave]
_, var = variableMapa[clave]

Nota: El uso del carácter subrayado (_) permite omitir un valor retornado, en este caso el nombre de la propia clave.

Si queremos recorrer uno a uno los elementos utilizaremos un bucle for alimentado por un rango mediante range, al cual indicaremos el mapa.

for varClave, varValor := range variableMapa {
   ...
}

Nota: El bloque for se repetirá por cada uno de los elementos del mapa. El propio bucle obtendrá, de cada elemento, dos variables: la clave y el valor. También podemos hacer uso del carácter subrayado (_) para omitir uno de estos valores.

Ejemplo

El siguiente ejemplo muestra los conceptos vistos anteriormente sobre los mapas en Go.

package main

import "fmt"

func main() {
   // Definicion del mapa
   centros := make(map[string]string)

   // Adicion de claves y valores
   centros["MADE"] = "Madrid Este"
   centros["MADN"] = "Madrid Norte"
   centros["MOS"] = "Mostoles"
   centros["POZ"] = "Pozuelo"
   centros["BCN"] = "Barcelona"

   // Visualizacion de propiedades y valores
   fmt.Println("Centros: ", centros)
   fmt.Println("Total: ", len(centros))
   fmt.Println("MOS: ", centros["MOS"])

   // Cambio de un elemento por su clave
   centros["POZ"] = "Pozoblanco"

   // Eliminacion de un elemento por su clave
   delete(centros, "MADN")

   // Visualizacion de propiedades y valores
   fmt.Println("Centros: ", centros)
   fmt.Println("Total: ", len(centros))

   // Verificar si existe (hay) valor para un elemento
   clave, existe := centros["MOS"]
   fmt.Printf("Clave: %s, existe: %v\n", clave, existe)
   _, existe = centros["ZZZ"]
   fmt.Printf("existe: %v\n", existe)

   // Recorrido del mapa
   for key, value := range centros {
      fmt.Printf("Key: %s, Value: %s\n", key, value)
   }
}

Nota: Los elemetos del mapa no están ordenados. Cuando se accede al mapa, estos elementos pueden estar en diferentes posiciones.

El resultado del programa será el siguiente:

$ go run maps.go
Centros:  map[MADE:Madrid Este MADN:Madrid Norte 
MOS:Mostoles POZ:Pozuelo BCN:Barcelona]
Total:  5
MOS:  Mostoles
Centros:  map[MOS:Mostoles POZ:Pozoblanco BCN:Barcelona 
MADE:Madrid Este]
Total:  4
Clave: Mostoles, existe: true
existe: false
Key: BCN, Value: Barcelona
Key: MADE, Value: Madrid Este
Key: MOS, Value: Mostoles
Key: POZ, Value: Pozoblanco

Enlaces de interés

jueves, 8 de noviembre de 2018

Operadores lógicos en Go

Los operadores lógicos o relacionales, permiten evaluar uno o dos valores, pero, a diferencia de los operadores de comparación, ni realiza una comparación ni evalúa valores numéricos o de cadena, si no valores exclusivamente lógicos.

La utilidad de estos operadores es evaluar condiciones complejas, en las que existen varias circunstancias, estableciendo una relación entre ellas.

Los operadores lógicos o relacionales en Go son los siguientes:

valor1 && valor2   AND/Y
valor2 || valor2   OR/O
!valor             NOT/NO (negación)

Operador && (AND/Y)

El operador && retornará un valor true si los dos valores lógicos comparados valen true.

true  && true   = true
true  && false  = false
false && true   = false
false && false  = false

Operador || (OR/O)

El operador || retornará un valor true si, al menos uno de los dos valores lógicos comparados vale true.

true  || true   = true
true  || false  = true
false || true   = true
false || false  = false

Operador ! (NOT/NO)

El operador ! retornará el valor contrario al evaluado (niega el valor).

!true  = false
!false = true

Precedencia de los operadores

Cuando se evalúa más de una relación, el orden de precedencia será de izquierda a derecha. Se evalúan los dos primeros valores, y el resultado de éste, se evaluará con el tercero, y así, sucesivamente, hasta el final.

true || false && false || true

// Paso a paso
[true || false] && false || true
true && false || true
[true && false] || true
false || true
true
Nota: Se ha utilizado los corchetes para resaltar qué operación se va a realizar.

Si se utiliza los paréntesis, las evaluaciones englobadas entre ellos se priorizarán, también de izquierda a derecha.

true || (false && false) || true || (true && false)

// Paso a paso
true || (false && false) || true || (true && false)
true || [(false && false)] || true || (true && false)
true || false || true || (true && false)
true || false || true || [(true && false)]
true || false || true || false
[true || false] || true || false
true || true || false
[true || true] || false
true || false
true

Ejemplo

package main

import "fmt"

func main() {
   var (
      edadRafael int = 47
      edadNerea  int = 16
      edadGoyi   int = 69
   )

   fmt.Println("Rafael en edad de trabajar: ",
      edadRafael >= 16 && edadRafael <= 67)
   fmt.Println("Goyi en edad de trabajar: ",
      edadGoyi >= 16 && edadGoyi <= 67)
   fmt.Println("Nerea en edad de trabajar: ",
      edadNerea >= 16 && edadNerea <= 67)

   fmt.Println("Rafael mayor de edad: ", !(edadRafael < 18))
   fmt.Println("Goyi mayor de edad: ", !(edadGoyi < 18))
   fmt.Println("Nerea mayor de edad: ", !(edadNerea < 18))

   fmt.Println("true || false && false || true - ",
      true || false && false || true)
   fmt.Println("true || (false&&false) ||true|| (true&&false) - ",
      true || (false && false) || true || (true && false))
}

El resultado será el siguiente:

$ go run oper-logicos.go
Rafael en edad de trabajar:  true
Goyi en edad de trabajar:  false
Nerea en edad de trabajar:  true
Rafael mayor de edad:  true
Goyi mayor de edad:  true
Nerea mayor de edad:  false
true || false && false || true -  true
true || (false && false) || true || (true && false) -  true

Enlaces de interés

Operadores de comparación en Go

Los operadores de comparación permiten evaluar una condición o circunstancia, comparando dos valores. El resultado de esta evaluación es un valor lógico (true o false).

Los operadores de comparación en Go son los siguientes:

valor1 == valor2  igual a 
valor1 != valor2  distinto de (no igual a)
valor1 <  valor2  menor que
valor1 <= valor2  menor o igual que
valor1 >  valor2  mayor que
valor1 >= valor2  mayor o igual que

Ejemplo

El siguiente ejemplo ilustra todos estos operadores comparando la edad de una persona con el valor 18.

package main

import "fmt"

func main() {
   var edad int = 18

   fmt.Println("edad == 18 - ", edad == 18)
   fmt.Println("edad != 18 - ", edad != 18)
   fmt.Println("edad <  18 - ", edad < 18)
   fmt.Println("edad <= 18 - ", edad <= 18)
   fmt.Println("edad >  18 - ", edad > 18)
   fmt.Println("edad >= 18 - ", edad >= 18)
}

No hay que confundir los operadores de asignación (= y :=) con el operador de comparación de igualdad (==).

El resultado será el siguiente:

$ go run oper-comparacion.go
edad == 18 -  true
edad != 18 -  false
edad <  18 -  false
edad <= 18 -  true
edad >  18 -  false
edad >= 18 -  true

Enlaces de interés

martes, 6 de noviembre de 2018

Operadores de concatenación en Go

La concatenación es una operación de unión de varios strings o cadenas de texto.

Existen dos operadores de concatenación en Go:

string1 + string2   // Concatenación
variable += string  // Concatenación y asignación

He aquí un ejemplo:

package main

import "fmt"

func main() {
   var cad1 string = "En un lugar de la Mancha, "
   var cad2 string = "de cuyo nombre no quiero acordarme"

   frase := cad1 + cad2

   fmt.Println(frase)

   saludo := "Hola, "
   saludo += "amigo"

   fmt.Println(saludo)
}

El resultado es el siguiente:

$ go run concatenacion.go
En un lugar de la Mancha, de cuyo nombre no quiero acordarme
Hola, amigo

Enlaces de interés

Operadores de asignación, asignación aritmética e incremento en Go

Operadores de asignación

Un operador de asignación realiza la operación de asignar un valor a una variable o a una constante. Su sintaxis es la siguiente:

variable/constante operador valor

Mediante el operador = igualamos (o asignamos) la variable o constante con el valor especificado a continuación.

var op1 int
op1 = 1

El operador de asignación = sólo permite asignar valores a variables o constantes que ya han sido declaradas previamente especificando su tipo, tal y como se representa en el ejemplo anterior. El valor a asignar debe ser del mismo tipo que el de la variable declarada.

Hay otro operador de asignación en Go, que permite asignar, por inferencia, un valor a una variable que se está declarando o creando en ese mismo momento, sin especificar su tipo.

pi := 3.14159

En este ejemplo declaramos la variable pi y le asignamos el valor 3.14159. El compilador de Go detecta automáticamente el tipo del valor que se asigna, e internamente, crea la variable pi de tipo float64 para que tenga el mismo tipo que el dato. El compilador de Go, internamente realizaría la siguiente declaración y asignación:

var pi float64 = 3.14159

Operadores de incremento y decremento

Go posee dos operadores que permiten incrementar o decrementar el valor de una variable:

variable++
variable--

Estos operadores necesitan únicamente un operando, que sería la variable sobre la cual incrementar o decrementar en uno su valor.

Operadores de asignación aritmética

Un operador de asignación aritmética permite, en un único paso, realizar una operación aritmética sobre una variable y guardar el resultado en dicha variable.

variable operador valor

En la siguiente tabla se muestra la equivalencia de estos operadores:

variable += valor  -> variable = variable + valor
variable -= valor  -> variable = variable - valor
variable *= valor  -> variable = variable * valor
variable /= valor  -> variable = variable / valor
variable %= valor  -> variable = variable % valor

He aquí un ejemplo:

var op1 int = 3
var op2 int = 2
op1 *= op2   // op1 = op1 * op2 -> 6

Ejemplo

package main

import "fmt"

func main() {
   // Asignacion normal
   var op1 int
   op1 = 1

   // Asignacion por inferencia
   pi := 3.14159
   fmt.Printf("pi: %T %v\n", pi, pi)

   // Incremento
   op1++    // op1=op1+1 >> 2

   // Asignacion aritmetica
   op1 += 4 // op1=op1+4 >> 6
   op1 -= 2 // op1=op1-2 >> 4
   op1 *= 6 // op1=op1*6 >> 24
   op1 /= 2 // op1=op1/2 >> 12

   // Decremento
   op1--    // op1=op1-1 >> 11
   fmt.Println("op1 = ", op1)

   // Aritmetica con una constante
   const DUP int = 2
   op1 *= DUP  // op1=op1*DUP >> 22

   fmt.Println("op1 = ", op1)
}

El resultado es el siguiente:

pi: float64 3.14159
op1 =  11
op1 =  22

Enlaces de interés

Operadores aritméticos y expresiones en Go

Un operador permite realizar una operación (de ahí su nombre) sobre uno o varios valores (operandos), que pueden ser datos directos, variables o constantes.

Operadores aritméticos

Los operadores artiméticos en Go son:

+  Suma
-  Resta
*  Multiplicación
/  División
%  Resto (de la división)

Estos operadores permiten realizar operaciones aritméticas con valores numéricos.

Expresiones

Una expresión se refiere a la transcripción de una o varias operaciones mediante código, utilizando para ello operadores, variables, constantes, valores, etc.

Para que un operador aritmético pueda desempeñar su función, es necesario expresar la operación de la siguiente manera:

operando1 operador operando2

Los operandos son los valores que intervienen en la operación. Pueden ser valores directos, variables o constantes.

Ejemplo simple

El siguiente ejemplo muestra el uso de los operadores aritméticos y de las expresiones, las cuales realizarán el calculo determinado por el operador, y el resultado se asignará a una variable.

package main

import "fmt"

func main() {
   var op1, op2 int = 6, 4
   var suma int = op1 + op2
   var resta int = op1 - op2
   var multiplicacion int = op1 * op2
   var division int = op1 / op2
   var resto int = op1 % op2

   fmt.Printf("%v + %v = %v\n", op1, op2, suma)
   fmt.Printf("%v - %v = %v\n", op1, op2, resta)
   fmt.Printf("%v * %v = %v\n", op1, op2, multiplicacion)
   fmt.Printf("%v / %v = %v\n", op1, op2, division)
   fmt.Printf("%v resto %v = %v\n", op1, op2, resto)
}

El resultado será el siguiente:

$ go operadores-aritmeticos.go
6 + 4 = 10
6 - 4 = 2
6 * 4 = 24
6 / 4 = 1
6 resto 4 = 2

Precedencia de los operadores

Cuando escribimos expresiones más complejas, donde se desarrollan varias operaciones, el orden de precedencia será siempre de izquierda a derecha.

fmt.Println("4 + 3 - 1 = ", 4+3-1)

En este caso, primero se sumarán 4 y 3 (7), y después se restará 1, dando como resultado el valor 6.

Sin embargo, según el operador, la precedencia cambia. Por ejemplo, el siguiente ejemplo:

fmt.Println("4 + 2 * 6 - 8 / 2 + 4 % 2 = ", 4+2*6-8/2+4%2)

daría como resultado 12

En esta expresión tan larga, primero se calcularían las expresiones con el operador de mayor peso, y después, con los resultados, se calcularían las expresiones de menor peso, siempre de izquierda a derecha.

Los operadores de mayor peso son la multiplicación, la división y el resto. Los operadores de menor peso serían la suma y la resta.

Por tanto, en el ejemplo anterior se calcularían primero, de izquierda a derecha, las operaciones de mayor peso. Una vez calculadas, de izquierda a derecha, se calcularían las operaciones de menor peso.

4 + [2 * 6] - [8 / 2] + [4 % 2]
4 + 12 - 4 + 0
12

Nota: Se han enmarcado entre corchetes las operaciones de mayor peso para identificar claramente cómo se realizan los cálculos

La precedencia de los paréntesis

Los paréntesis permiten cambiar la precedencia de los operadores, obteniendo el mayor peso en la precedencia. Por tanto, primero se calculan los paréntesis, después la multiplicación, división o resto, y, por último, la suma o la resta.

fmt.Println("(4 + 2) * (6 - 8) / 2 + 4 = ", (4+2)*(6-8)/2+4)

El resultado de la expresión anterior será -2. El orden de las operaciones es el siguiente:

(4 + 2) * (6 - 8) / 2 + 4
(6) * (-2) / 2 + 4
-12 / 2 + 4
-6 + 4
-2

El paréntesis no tiene por qué englobar sólo una operación. Puede tener las operaciones que sean necesarias, e incluso englobar entre paréntesis operaciones dentro de otra operación englobada por paréntesis. Aquí el orden de precedencia serían desde los paréntesis más profundos hasta los más externos. Veamos un ejemplo:

fmt.Println("(4 + 2) * ((6 - 8 / 2) + 4) = ", (4+2)*((6-8/2)+4))

El orden de las operaciones es el siguiente:

(4 + 2) * ((6 - 8 / 2) + 4)
(4 + 2) * ((6 - [8 / 2]) + 4)
(4 + 2) * ((6 - 4) + 4)
(4 + 2) * (2 + 4)
(6) * (6)
36

Enlaces de interés

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

miércoles, 31 de octubre de 2018

Arrays en Go: strings como arrays y extracción de elementos

Los strings o cadenas de texto son, internamente, arrays de caracteres, por lo que podemos extraer caracteres de la misma manera como extraemos datos de otros arrays.


Extracciones de datos de un array

Existen diversas formas de extraer datos de un array. La primera es la más sencilla: accediendo al elemento que se encuentra en el índice especificado entre corchetes.

array[n]

donde n es un número entre cero y longitud -1, y se refiere al índice del array.

Otra forma de extraer elementos de un array es mediante rangos, indicando una posición inicial y una posición final.

array[inicio:fin]

En esta especificación, la posición inicial está incluida en el rango, mientras que la posición final no está incluida, llegando hasta el elemento justamente anterior.

En un rango, si no se especifica la posición inicial, asume por defecto la primera posición (posición cero). Si no se especifica la posición final, asume por defecto la longitud del array. Si no se especifica ninguna de las dos, asume desde el inicio (posición 0) hasta el final (longitud total).

El siguiente ejemplo ilustra estos conceptos, tanto con un string como con un array de enteros.

package main

import "fmt"

func main() {
   var poblacion string = "Arroyomolinos"
   notas := [6]int{6, 7, 9, 8, 5, 3}

   fmt.Printf("poblacion: %v\n", poblacion)
   fmt.Printf("Longitud poblacion: %v\n", len(poblacion))
   fmt.Printf("Carácter 4: %v\n", string(poblacion[4]))
   fmt.Printf("poblacion[:6] %v\n", poblacion[:6])
   fmt.Printf("poblacion[2:6] %v\n", poblacion[2:6])
   fmt.Printf("poblacion[6:] %v\n", poblacion[6:])
   fmt.Printf("poblacion[:] %v\n", poblacion[:])
   fmt.Println("---")
   fmt.Printf("notas: %v\n", notas)
   fmt.Printf("Longitud notas: %v\n", len(notas))
   fmt.Printf("Capacidad notas: %v\n", cap(notas))
   fmt.Printf("notas[4]: %v\n", notas[4])
   fmt.Printf("notas[:3] %v\n", notas[:3])
   fmt.Printf("notas[2:4] %v\n", notas[2:4])
   fmt.Printf("notas[3:] %v\n", notas[3:])
   fmt.Printf("notas[:] %v\n", notas[:])
}

Es conveniente reseñar varias cosas sobre los strings:

  • Cuando se extrae un único elemento, este será un byte, es decir, un entero de 8 bits (valor entre 0 y 255). Este byte se corresponde al código ASCII del carácter extraído. Por dicho motivo, en el código forzamos a que dicho byte se convierta y se muestre como un string.
  • Cuando se extrae un rango del string, dicho rango es otro string con el rango de caracteres extraído
  • Puede aplicarse la función len() (longitud o tamaño), pero no la función cap() (capacidad).

El resultado será el siguiente:

$ go run array-string.go
poblacion: Arroyomolinos
Longitud poblacion: 13
Carácter 4: y
poblacion[:6] Arroyo
poblacion[2:6] royo
poblacion[6:] molinos
poblacion[:] Arroyomolinos
---
notas: [6 7 9 8 5 3]
Longitud notas: 6
Capacidad notas: 6
notas[4]: 5
notas[:3] [6 7 9]
notas[2:4] [9 8]
notas[3:] [8 5 3]
notas[:] [6 7 9 8 5 3]

martes, 30 de octubre de 2018

Arrays en Go: estructuras

Los tipos definidos, como las estructuras, pueden ser utilizados también para crear colecciones, con lo que las posibilidades en la gestión de datos aumentan.

Arrays de estructuras

Vamos a tratar una colección de empleados, y para ello, en lugar de tener los diferentes datos de cada empleado en un array propio, vamos a definir una estructura de datos y crear un array basado en dicha estructura.

package main

import "fmt"

func main() {

   // Definicion de la estructura Empleado
   type Empleado struct {
      nombre   string
      edad     int
      esCasado bool
   }

   // Declaracion del array de estructura
   var empleados [3]Empleado

   // Asignacion campo a campo
   empleados[0].nombre = "Rafael"
   empleados[0].edad = 47
   empleados[0].esCasado = true

   // Asignacion mediante un objeto JSON
   empleados[1] = Empleado{
      nombre:   "Eduardo",
      edad:     48,
      esCasado: false,
   }

   // Asignacion mediante un objeto JSON
   empleados[2] = Empleado{
      nombre:   "Nerea",
      edad:     17,
      esCasado: false}

   // Visualizacion de resultados
   fmt.Println("Array empleados = ", empleados)
   fmt.Println("Primer empleado: ", empleados[0])
   fmt.Println("Nombre primer empleado: ", empleados[0].nombre)
}

Por convención, para una lectura natural, el nombre de una estructura se declara con la primera letra en mayúscula y en singular, ya que es el molde de cada uno de las entidades de datos que después se generarán.

El nombre del array se declara en plural, ya que contendrá varios empleados.

Para la asignación de los tres empleados hemos utilizado diferentes formatos. El primero es accediendo individualmente a cada uno de sus campos, pero previamente hay que identificar el elemento del array a través de su índice.

El segundo y tercer elemento se asigna mediante un objeto en formato JSON, y para ello hay que referenciar el elemento del array mediante su índice, y, en la asignación, especificar el nombre de la estructura antes del objeto JSON (el cual se escribe entre llaves), para obligar a que dicho objeto JSON tenga el mismo tipo que la estructura Empleado, es decir, que contenga exactamente los mismos campos y el mismo tipo.

La diferencia de asignación de datos entre el segundo y tercer elemento es que el último campo del segundo elemento termina con una coma y la llave está en la línea siguiente, mientras que el último campo tercer elemento termina con la llave de cierre del elemento JSON.

Por último, se visualiza el contenido del array, el contenido del primer elemento del array y el valor del campo nombre del primer elemento del array. El resultado será el siguiente:

$ go run arrays.go
Array empleados =  [{Rafael 47 true} {Eduardo 48 false} {Nerea 17 false}]
Primer empleado:  {Rafael 47 true}
Nombre primer empleado:  Rafael

Enlaces de interés

Arrays en Go: multidimensionalidad

Los arrays no son una mera colección secuencial de valores. También permiten con trabajar varias dimensiones de datos, con lo que su capacidad de trabajar con datos relacionados aumenta exponencialmente.


Arrays bidimensionales

Imaginemos una clase que tiene 20 alumnos, que todos tienen 11 asignaturas, y que deseamos guardar las notas de cada uno de estos alumnos.

Go nos permite definir y trabajar con un array en dos dimensiones (una para los alumnos, y otra para las asignaturas). Para ello, a la hora de declarar el array, definimos el tamaño de cada una de las dimensiones entre corchetes.

var notas [20][11]int   // 20 alumnos y 11 asignaturas

A la hora de acceder a cada elemento, especificamos el índice correspondiente en cada una de las dos dimensiones. Por ejemplo:

notas[0][0] = 5 // Nota 5 para alumno 0, asignatura 0
notas[5][6] = 7 // Nota 7 para alumno 5, asignatura 6
notas[11][2] = 6 // Nota 6 para alumno 11, asignatura 2

El siguiente programa ilustra el uso de un array bidimensional de 3 alumnos con 3 asignaturas (para no hacerlo muy extenso).

package main

import "fmt"

func main() {
   // Definicion de un array bidimensional
   // Primera dimension es el alumno
   // Segunda dimension es la asignatura
   var notas [3][3]int

   // Asignacion nota a nota
   notas[0][0] = 5 // alumno 0, asignatura 0
   notas[0][1] = 3 // alumno 0, asignatura 1
   notas[0][2] = 6 // alumno 0, asignatura 2

   // Asignacion directa de todas las notas
   notas[1] = [3]int{7, 9, 6}  // Alumno 1
   notas[2] = [3]int{6, 7, 8}  // Alumno 2

   fmt.Println("---")
   fmt.Println("Contenido array notas", notas)
   fmt.Println("Contenido primer alumno", notas[0])
   fmt.Println("---")
   fmt.Println("Notas del primer alumno")
   fmt.Println("Asignatura 0: ", notas[0][0])
   fmt.Println("Asignatura 1: ", notas[0][1])
   fmt.Println("Asignatura 2: ", notas[0][2])
}

El resultado sería el siguiente:

---
Contenido array notas [[5 3 6] [7 9 6] [6 7 8]]
Contenido primer alumno [5 3 6]
---
Notas del primer alumno
Asignatura 0:  5
Asignatura 1:  3
Asignatura 2:  6

Arrays multidimensionales

Podemos utilizar más de dos dimensiones en un array. Por ejemplo, si vamos a asignar las notas de varias clases, podríamos utilizar un array tridimensional:

// 6 clases, 20 alumnos y 11 asignaturas
var notas [6][20][11]int

El número de dimensiones puede ser mayor. Por ejemplo, si tenemos varios centros, podemos añadir una dimensión más:

// 3 centros, 6 clases, 20 alumnos y 11 asignaturas
var notas [3][6][20][11]int

La forma de referenciar y acceder a cada dimensión sigue la misma dinámica que con un array de dos dimensiones:

// Nota 5 para la asignatura 5, del alumno 14,
// de la clase 3, del centro 1
notas [1][3][14][5] = 5

El uso de arrays se vuelve más complejo y difícil de mantener a medida que añadimos dimensiones.

Además, el uso de dimensiones obliga a que todas las dimensiones sean iguales de forma obligatoria, cosa que no suele ser real. Por ejemplo, no todos los centros tienen el mismo número de clases, ni todas las clases tienen el mismo número de alumnos ni de asignaturas. Un año escolar puede tener más o menos clases que otro año, y lo mismo ocurre con los alumnos. Una legislación nueva puede incluir o quitar algunas asignaturas.

En estos casos es mejor utilizar colecciones de entidades por separado. Por ejemplo, una colección de centros, otra de clases, otra de alumnos y otra de asignaturas. Cada una de estas colecciones estará relacionada con otra a través de un campo clave, de tipo código o id. Esta sería la forma en que las bases de datos relacionales usan y conectan datos sin importar sus dimensiones.

Este tipo de colecciones ya utilizarían tipos definidos de estructura, los cuales simulan registros de campos variados similares a los registros de una tabla de base de datos.

Enlaces de interés

Arrays en Go: fundamentos

Los programas son capaces de manejar mucha información, y necesitaremos guardar y gestionar conjuntos de datos. Será muy habitual leer de un fichero o de una base de datos una colección de registros (por ejemplo de contactos) y mostrar en pantalla todos ellos, seleccionar alguno, consultarlo o modificarlo, y que se conserve en dicha colección.


Si una variable era como un cajón en la memoria en donde guardábamos un dato simple, un array es un archivador lleno de cajones del mismo tipo.

Un array se define como una variable, pero le damos una dimensión o tamaño mediante el uso de los corchetes ([tamaño]). Cuando se crea el array, por defecto contendrá el valor cero acorde al tipo de dato. Después, se puede consultar o modificar uno de los datos refiriéndonos a la variable y al índice utilizando los corchetes ([índice]). Un índice es un identificador del cajón dentro del archivador, comenzando siempre desde cero, secuencialmente, hasta el último (tamaño menos uno (n - 1)).

package main

import "fmt"

func main() {

   // Creacion del array, reservando primero
   // y asignando despues
   var nombre [3]string   // Reserva 3 elementos

   nombre[0] = "Rafael"   // Primer elemento del array
   nombre[1] = "Eduardo"  // Segundo elemento del array
   nombre[2] = "Nerea"    // Tercer elemento del array

   fmt.Printf("Primer nombre: %v\n", nombre[0])
   fmt.Printf("Segundo nombre: %v\n", nombre[1])
   fmt.Printf("Tercer nombre: %v\n", nombre[2])
}

En este ejemplo, primero creamos el array, reservando 3 elementos, con valores cero (en el caso de un string, el valor cero es una cadena vacía). Después, vamos asignando, uno a uno, los valores en cada elemento del array, referenciándolos mediante el índice, el cual va desde la posición cero (primer elemento) hasta la posición dos (tercer y último elemento). Por último, visualizamos en pantalla cada uno de los valores, referenciándolos mediante el índice correspondiente.

También es posible crear el array y asignar todos los valores en un mismo paso.

package main

import "fmt"

func main() {

   // Creacion y asignacion del array
   edad := [3]int{47, 48, 17}

   fmt.Printf("Primera edad: %v\n", edad[0])
   fmt.Printf("Segunda edad: %v\n", edad[1])
   fmt.Printf("Tercera edad: %v\n", edad[2])
}

En la primera línea declaramos, por inferencia, la variable edad, definimos el tamaño del array ([3]), su tipo (int) y la asignación de un array con los valores correspondientes.

La representación de un array con valores se realiza mediante llaves, encerrando los valores separados por comas ({valor1, valor2, ...}).

Características particulares de un array

La principal característica de un array es que permite almacenar una colección de datos del mismo tipo, y acceder a cada uno de estos datos para su consulta o modificación.

Sin embargo, la principal limitación de un array es que, una vez declarado, su tamaño no puede variar (tamaño fijo). Esto nos obliga a saber de antemano el tamaño del array o trabajar con un tamaño fijo para siempre.

Existen dos características particulares de una colección en Go: la longitud y la capacidad.

La longitud se refiere al número de elementos utilizados de la colección, mientras que la capacidad se refiere al número total de elementos de la colección. En el caso de un array, ambos conceptos son equivalentes, y coincide con el tamaño del array.

Nota: Más adelante, en este tutorial, aprenderemos otro tipo de colección llamado slices, en donde estas características tienen un sentido más profundo y concreto.

El lenguaje Go proporciona dos funciones para conocer la longitud y la capacidad de una colección: len() y cap(). En ambas funciones se especifica el nombre de la colección entre paréntesis.

package main

import "fmt"

func main() {

   // Creacion y asignacion del array
   edad := [3]int{47, 48, 17}

   fmt.Printf("Primera edad: %v\n", edad[0])
   fmt.Printf("Segunda edad: %v\n", edad[1])
   fmt.Printf("Tercera edad: %v\n", edad[2])
   fmt.Printf("Contenido del array: %v\n", edad)
   fmt.Printf("Tamaño del array: %v\n", len(edad))
   fmt.Printf("Capacidad del array: %v\n", cap(edad))
}

El resultado será el siguiente:

$ go run arrays.go
Primera edad: 47
Segunda edad: 48
Tercera edad: 17
Contenido del array: [47 48 17]
Tamaño del array: 3
Capacidad del array: 3

Enlaces de interés