2️⃣

Clase 2: Control de flujo y estructuras de datos


Objetivos
  1. Aprender a crear y manipular arrays y slices en Go, realizando operaciones como inserción, eliminación y actualización de elementos.
  1. Comprender y aplicar las operaciones básicas en maps, como la obtención de un valor dado su clave y la verificación de la existencia de una clave en el map.
  1. Comprender los conceptos fundamentales del control de flujo en Go, incluyendo las instrucciones condicionales (if-else, switch), los bucles (for) y las instrucciones de salto (break, continue).

Contenido de la clase:

1. Arrays y slices: creación, manipulación y operaciones básicas.

Los arrays y los slices son estructuras de datos fundamentales en la programación.

  • Arrays:
    • Son una secuencia contigua de elementos del mismo tipo con tamaño fijo.
    • Se definen utilizando una longitud específica durante la declaración.
    • Los arrays tienen un tamaño estático que no puede modificarse una vez creado.
    • La copia de un array implica copiar todos sus elementos.
    • La asignación entre arrays implica copiar todos sus elementos.
    • El acceso a los elementos se realiza mediante índices.
    • La longitud de un array se obtiene utilizando la función len().

package main
import "fmt"

func main() {
	// Declarando array - Se inicializa con 0 todos los elementos por defecto
	var numbers [3]int
	// Reasignando valores
	numbers[0] = 1
	numbers[1] = 2
	numbers[2] = 3 
	fmt.Println(numbers) // [1 2 3]

	var students [3]string = [3]string{"Carlos", "Juan", "Jose"}
	fmt.Println(students) // [Carlos Juan José]
	
	// Asignación corta y usando indices
	cart := [3]string{"Eggs", 2: "Milk"}
	fmt.Println(cart) // [Eggs "String Vacio" Milk]

	// Sin una longitud establecida
	courses := [...]string{"Go", "Nest"}
	fmt.Println("Length: ", len(courses)) // 2
	fmt.Printf("%T \n", courses) // [2]string
	newCourses := courses // Diferentes espacios de memoria
	newCourses[0] = "JS"
	fmt.Println(courses, newCourses) // [Go Nest] [JS Nest]
}

El tamaño del array es una parte del tipo. Por eso [5]int y [25]int son tipos distintos. No te preocupes por esta restricción ya que existen los slices que no tienen esa restricción.

  • Slices:
    • Son una vista flexible y dinámica de una sección de un array.
    • Se crean a partir de un array existente o utilizando la función make().
    • Los slices no tienen un tamaño fijo y pueden crecer o reducirse dinámicamente.
    • Comparten memoria con el array subyacente, por lo que las modificaciones en un slice afectan al array original y viceversa.
    • La copia de un slice solo copia la referencia al array subyacente, no los elementos en sí.
    • La asignación entre slices simplemente copia la referencia al array subyacente.
    • El acceso a los elementos se realiza mediante índices.
    • La longitud de un slice se obtiene utilizando la función len(), y la capacidad se obtiene utilizando la función cap().
    • Los slices admiten operaciones adicionales, como agregar elementos con append(), recortar un slice con slicing y copiar slices con copy().
    • Para eliminar un índice particular, se debe formar un slice a partir de otros slices.
package main
import "fmt"

func main() {
	// Declaración de un slice
	var mySlice []int
	fmt.Println(mySlice)

	// Crear un slice
	// Slice a partir de un array
	var names = [3]string{"Julio", "Claudio", "Oscar"}
	newNames := names[:]
	newNames[0] = "Osman" // Modifica al Array original y se modifica el Slice actual
	fmt.Println(names, newNames, len(names), cap(names))

	// Slice utilizando la función make(tipo_dato, len, cap(optional))
	courses := make([]int, 3, 5)
	fmt.Println(courses, len(courses), cap(courses))

	// Añadir un elemento a un slice
	// La función append es una función variádica, es decir que se le pueden enviar N argumentos al mismo tiempo.
	var list []int = []int{1, 2, 3, 4, 5}
	fmt.Println(list) // [1, 2, 3, 4, 5]
	// list = append(list, 6, 7, 8, 9)
	list2 := []int{6, 7, 8, 9}
	// Agregando elementos de un Slice a otro con el operador elipsis
	list = append(list, list2...) // [6, 7, 8, 9]
	fmt.Println(list)

	// Crear un slice de dos dimensiones
	slice2D := [][]int{
		{1, 2, 3},
		{4, 5, 6},
		{7, 8, 9},
	}
	// Agregar nuevo elemento
	newSlice := []int{10, 11}
	slice2D = append(slice2D, newSlice)
	// Acceder a los elementos del slice de dos dimensiones
	fmt.Println(slice2D[0][0]) // 1
	fmt.Println(slice2D[1][1]) // 5
	fmt.Println(slice2D[2][2]) // 9
	// Modificar un elemento del slice de dos dimensiones
	slice2D[1][2] = 99
	fmt.Println(slice2D)
}

En resumen, los arrays son estructuras estáticas con tamaño fijo, mientras que los slices proporcionan una vista más flexible y dinámica de una sección de un array. Los slices son más utilizados en Go debido a su capacidad de crecimiento y su facilidad para manipular y operar con ellos.

2. Maps: uso y manipulación de datos clave-valor.

El tipo de dato map se utiliza para trabajar con datos clave-valor. Un map es una estructura de datos que almacena pares de valores, donde cada valor está asociado a una clave única. Esto permite acceder rápidamente a los valores utilizando las claves correspondientes.

  • Características
    • Los maps no se pueden copiar directamente, se tiene que hacer mediante un bucle (for)
    • Las claves en un map deben ser únicas. Esto significa que no puedes tener dos claves idénticas en un map. Si intentas agregar una clave existente, el valor anterior asociado con esa clave se reemplazará con el nuevo valor.
    • Se accede a cada elemento mediante su clave (key)
    • Puedes obtener la longitud de un map utilizando la función len(map).
    • Puedes utilizar un loop for y la sintaxis range para iterar sobre los elementos de un map.
    • Puedes eliminar elementos de un map utilizando la función delete(map, clave).
package main
import "fmt"

func main() {
	// Crear un map vacío
	students := make(map[string]int)

	// Agregar elementos al map
	students["Juan"] = 18
	students["Pedro"] = 25
	students["Maria"] = 40
	fmt.Println(students)

	// Acceder a un valor utilizando una clave
	ageJuan := students["Juan"]
	fmt.Println("La edad de Juan es:", ageJuan)

	// Modificar un valor existente
	students["Pedro"] = 55
	fmt.Println(students)

	// Eliminar elemento del map
	delete(students, "Maria")
	fmt.Println(students)

	// Verificar si una clave existe en el map
	ageJose, exists:= students["Jose"]
	if exists {
		fmt.Println("La edad de Jose es:", ageJose)
	} else {
		fmt.Println("Jose no está en el map")
	}
	
	// Iterar sobre los elementos del map
  for name, age := range students {
      fmt.Println(name, "tiene", age , "años")
  }
}

Recuerda que los maps en Go no mantienen un orden específico de los elementos, por lo que el orden en el que se iteran los elementos puede variar en cada ejecución.

Los maps en Go son una herramienta poderosa para trabajar con datos clave-valor, y ofrecen un rendimiento eficiente en la búsqueda y manipulación de datos.

3. Estructuras de control: condicionales y bucles.
  • ¿Qué son las estructuras de control?
    • Las estructuras de control permiten controlar el flujo de la ejecución del código. Para poder construir programas es necesario el uso de estas de forma que representan la lógica de “que hacer en caso de” y “a donde ir”
  • ¿Qué tipos de estructuras de control existen en Go?
    • Condicionales
      • If: Permite ejecutar un bloque de código si una condición es true.
      • Else: Se utiliza junto con if para ejecutar un bloque de código como alternativa si la condición en el if es false.
      • Else If: Premite evaluar multiples condiciones en cadena y ejecutar el bloque de código correspondiente a la primera condición que sea verdadera.
      • Switch: Permite seleccionar un caso específico de varios posibles, basándose en el valor de una expresión.

      If / Else / Else If

      La sentencia if es la más sencilla de utilizar y se encuentra en casi todos los lenguajes de programación. Representa el paradigma de lo que sucede si una condición se cumple y en combinación con la sentencia else puede especificar un camino alternativo.

      La sentencia else que se puede traducir como “si no…” o “de lo contrario…”, permite definir un bloque de código en caso de que la condición if no se cumpla.

      package main
      import "fmt"
      
      func main() {
      	// Definimos una variable age tipo entero
      	var age int
      	fmt.Println("Cual es tu edad: ")
      
      	// Capturamos age en el puntero &age
      	// fmt.Scanln toma la dirección de memoria de la variable donde se almacenará el valor ingresado por el usuario
      	fmt.Scanln(&age)
      
      	// Evaluamos si age es mayor o igual a 18
      	if age >= 18 {
      		fmt.Println("Eres mayor de edad")	
      	} else {
      		fmt.Println("Eres menor de edad")	
      	}
      }

      Existe casos en los cuales lo que se requiere es poder evaluar múltiples condiciones, no solo dos.

      • Anidar un if dentro de otro
      • Utilizar else if para cubrir cada una de las condiciones
      package main
      import "fmt"
      
      func main() {
      	var movie string
      	fmt.Println("Escribe una categoria: terror | comedia | drama")
      	fmt.Scanln(&movie)
      	if movie == "terror" {
      		fmt.Println("Lista de peliculas de terror")
      	} else if movie == "comedia" {
      		fmt.Println("Lista de peliculas de comedia")
      	} else if movie == "drama" {
      		fmt.Println("Lista de peliculas de comedia")
      	} else {
      		fmt.Println("No existe esa categoría")
      	}
      }

      Switch

      La sentencia switch es equivalente al uso de múltiples else if, pero de una forma más ordenada.

      A diferencia de otros lenguajes de programación en los cuales el condicional switch requiere de la palabra reservada break, en Go no es necesario usarla.

      • Formas de utilizar switch
        1. switch con expresión
        1. switch sin expresión
        1. switch con múltiples valores en un caso
        1. switch con tipo de dato.
        1. switch con fallthrough (fallthrough es usada para transferir el control a la primera declaración del case que está presente inmediatamente después del case que se ha ejecutado. Esta solo se puede usar como la declaración final en una cláusula (al final de cada case).
      package main
      import "fmt"
      
      func main() {
      	// Switch con expresión
      	// También se puede declarar la variable primero fruit := "manzana"
      	switch fruit := "manzana"; fruit {
      	case "manzana":
      		fmt.Println("Es una manzana")
      	case "pera":
      		fmt.Println("Es una pera")
      	default:
      		fmt.Println("No es una fruta")
      	} // Es una manzana
      
      	// Switch sin expresión
      	number := 10
      	switch {
      	case number < 0:
      		fmt.Println("Es negativo")
      	case number > 0 && number < 100:
      		fmt.Println("Es positivo")
      	default:
      		fmt.Println("Es cero")
      	}
      
      	// Switch con múltiples valores en un caso
      	day := "lunes"
      	switch day {
      	case "lunes", "martes", "miercoles", "jueves", "viernes":
      		fmt.Println("Es un día laboral")
      	case "sabado", "domingo":
      		fmt.Println("Es un día de fin de semana")
      	default:
      		fmt.Println("No es un día válido")
      	}
      
      	// Switch con tipo de dato
      	var data interface{}
      	data = "Hola"
      
      	switch data.(type) {
      	case int:
      		fmt.Println("Es un entero")
      	case string:
      		fmt.Println("Es una cadena")
      	default:
      		fmt.Println("Tipo desconocido")
      	}
      
      	// Switch con fallthrough
      	days := 42
      
      	switch days {
      	case 42:
      		fmt.Println("42")
      		fallthrough
      	case 10:
      		fmt.Println("10")
      		fallthrough
      	case 20:
      		fmt.Println("20")
      	}
      }

      Las condicionales son una parte muy importante para cualquier programa sin importar el lenguaje de programación ya que nos permite tomar decisiones basadas en ciertas condiciones

    • Bucles
      • For: Se utilza para repetir N veces un bloque de código o mientras se cumple una condición.
      • For…range: Se utiliza para iterar sobre elementos de una estructura de datos, como un array, un slice, un map o un string.
      • Break: Permite salir de un bucle de manera prematura.
      • Continue: Permite saltar a la siguiente iteración del bucle sin ejecutar del resto de bloque de código.
      package main
      import "fmt"
      
      func main() {
      	// Opción 1 [FOR]
      	for i := 0; i < 5; i++ {
          if i == 2 {
            continue // Salta a la siguiente iteración si i es igual a 2
          }
      		fmt.Println(i)
      	}
      
      	// Opción 2 [SIMILAR A WHILE]
      	j := 0
      	for j < 5 {
      		fmt.Println(j)
      		j++
      	}
      
      	// Opción 3 [FOR INFINITO]
      	x := 0
      	for {
          fmt.Println(x)
          x++
          if x == 5 {
            break // Rompe el bucle cuando i es igual a 5
          }
      	}
      
      	// Opción 3 [FOR RANGE]
      	numbers := []int{1, 2, 3, 4, 5}
      	for index, value := range numbers {
      		fmt.Println("Índice:", index, "Valor:", value)
      	}
      }
    • Otros
      • goto: Permite saltar a una etiqueta específica en el código
      • defer: Se utilza para posponer la ejecución de una función hasta que la función que lo rodea termine.
      • panic y recover: se utilizan para manejar situaciones excepcionales(pacnic) y recuperarse de ellas(recover)

4. Tarea
  1. Escribe un programa en Go que imprima los números pares del 1 al 20.
// Solución
package main
import "fmt"

func main() {
	for i := 1; i <= 20; i++ {
		if i%2 == 0 {
			fmt.Println(i)
		}
	}
}
  1. Escribe un programa en Go que imprima los números del 1 al 100, pero para los múltiplos de 3 imprima "Fizz", para los múltiplos de 5 imprima "Buzz" y para los múltiplos de ambos 3 y 5 imprima "FizzBuzz".
// Solución
package main
import "fmt"

func main() {
	for i := 1; i <= 100; i++ {
		if i%3 == 0 && i%5 == 0 {
			fmt.Println("FizzBuzz")
		} else if i%3 == 0 {
			fmt.Println("Fizz")
		} else if i%5 == 0 {
			fmt.Println("Buzz")
		} else {
			fmt.Println(i)
		}
	}
}
5. Recursos