# Go desde Cero, Pero Bien: Ejercicios HackerRank (Vol. 1)

Go desde Cero, Pero Bien: Ejercicios HackerRank (Vol. 1)
Tabla de Contenidos

Bueno muchachones, aca les traigo un set de ejercicios para que practiquen, algunos son los basicos de HackerRank (Plataforma que les recomiendo muchisimo para que entrenen)

1. Simple Array Sum

Enunciado:

Dado un arreglo de enteros, tu tarea es encontrar la suma de sus elementos. Por ejemplo, si el arreglo es ar = [1, 2, 3], entonces 1 + 2 + 3 = 6, por lo que tu función debería devolver 6.

Explicación y Solución:

Este es para calentar motores. La idea es recorrer cada número del slice (que es como un arreglo pero más flexible en Go) y sumarlos. Usamos un bucle for para pasar por cada elemento.

package main
import "fmt"
/*
* La función simpleArraySum recibe un slice de enteros de 32 bits (int32)
* y devuelve la suma de sus elementos como un int32.
*/
func simpleArraySum(ar []int32) int32 {
// Creamos una variable 'suma' para ir guardando el total.
// La inicializamos en 0.
var suma int32 = 0
// 'range ar' nos devuelve dos valores en cada vuelta del bucle:
// el índice y el valor en esa posición. Como aquí no nos interesa
// el índice, lo ignoramos con un guion bajo '_'.
for _, valor := range ar {
// En cada vuelta, le sumamos el 'valor' actual a nuestra 'suma'.
suma += valor
}
// Al final, devolvemos la suma total.
return suma
}
func main() {
// Un slice de ejemplo para probar la función.
numeros := []int32{1, 2, 3, 4, 10, 11}
resultado := simpleArraySum(numeros)
fmt.Println("La suma del arreglo es:", resultado) // Salida: La suma del arreglo es: 31
}

Puntos clave:

  • Slice ([]int32): Es la estructura que usamos para guardar la colección de números.

  • Bucle for...range: La forma más cómoda y legible de recorrer slices, arreglos o mapas en Go.

  • Variable suma: Un acumulador para guardar el resultado parcial en cada paso.


2. Compare the Triplets

Enunciado:

Cesar y Lead crearon un problema para HackerRank. Un revisor califica los dos desafíos, otorgando puntos en una escala del 1 al 100 para tres categorías: claridad del problema, originalidad y dificultad.

La calificación del desafío de Cesar es el triplete a = (a[0], a[1], a[2]), y la calificación del desafío de Lead es el triplete b = (b[0], b[1], b[2]).

La tarea es encontrar sus puntos de comparación comparando a[0] con b[0], a[1] con b[1], y a[2] con b[2].

  • Si a[i] > b[i], Cesar recibe 1 punto.
  • Si a[i] < b[i], Lead recibe 1 punto.
  • Si a[i] = b[i], ninguna persona recibe un punto.

Los puntos de comparación son el total de puntos que una persona obtuvo. Devuelve un slice de enteros con la puntuación de Cesar en la primera posición y la de Lead en la segunda.

Explicación y Solución:

Aquí la clave es comparar elemento por elemento de los dos slices. Usamos un bucle for que vaya de 0 a 2 y, en cada pasada, un if/else if para ver quién gana el punto.

package main
import "fmt"
/*
* La función compareTriplets compara dos slices de tres elementos
* y devuelve los puntos de Cesar y Lead en un nuevo slice.
*/
func compareTriplets(a []int32, b []int32) []int32 {
// Creamos un slice para guardar los puntos.
// El primer elemento para Cesar, el segundo para Lead.
puntos := []int32{0, 0}
// Como sabemos que los slices siempre tienen 3 elementos,
// hacemos un bucle que se repita 3 veces (índices 0, 1, 2).
for i := 0; i < 3; i++ {
if a[i] > b[i] {
// Si el punto de Cesar es mayor, suma 1 a su contador.
puntos[0]++
} else if a[i] < b[i] {
// Si el punto de Lead es mayor, suma 1 al suyo.
puntos[1]++
}
// Si son iguales, no hacemos nada.
}
return puntos
}
func main() {
puntosCesar := []int32{17, 28, 30}
puntosLead := []int32{99, 16, 8}
resultado := compareTriplets(puntosCesar, puntosLead)
fmt.Printf("Puntos de Cesar: %d, Puntos de Lead: %d\n", resultado[0], resultado[1]) // Salida: Puntos de Cesar: 1, Puntos de Lead: 1
}

Puntos clave:

  • if/else if: La estructura de control perfecta para tomar decisiones basadas en las comparaciones.

  • Acceso por índice (a[i]): Accedemos a los elementos del slice usando su posición.

  • Incremento (++): Una forma corta y limpia de sumar 1 a una variable.


3. A Very Big Sum

Enunciado:

En este desafío, se te pide que calcules y muestres la suma de los elementos en un arreglo. La diferencia es que algunos de esos enteros pueden ser bastante grandes.

Debes usar un tipo de dato que pueda almacenarlos. En Go, int64 es perfecto para esto, ya que puede guardar números mucho más grandes que int32.

Explicación y Solución:

Es casi idéntico al primer ejercicio, pero con una diferencia clave: el tipo de dato. Usamos int64 para la suma y para los elementos del slice para evitar que los números se desborden (es decir, que se vuelvan demasiado grandes para el tipo de dato y causen resultados incorrectos).

package main
import "fmt"
/*
* aVeryBigSum funciona igual que simpleArraySum, pero usa int64
* para manejar números mucho más grandes.
*/
func aVeryBigSum(ar []int64) int64 {
// La variable suma también debe ser int64 para que quepa el resultado.
var suma int64 = 0
// El bucle for...range funciona exactamente igual.
for _, valor := range ar {
suma += valor
}
return suma
}
func main() {
numerosGrandes := []int64{1000000001, 1000000002, 1000000003, 1000000004, 1000000005}
resultado := aVeryBigSum(numerosGrandes)
fmt.Println("La suma grande es:", resultado) // Salida: La suma grande es: 5000000015
}

Puntos clave:

  • int64: El tipo de dato para enteros de 64 bits. ¡Esencial para no quedarse corto con números gigantes!

  • Consistencia de tipos: Si los números de entrada son int64, la variable que guarda la suma también debe serlo.


4. Diagonal Difference

Enunciado:

Dada una matriz cuadrada (una matriz de N x N), calcula la diferencia absoluta entre las sumas de sus diagonales.

Por ejemplo, la matriz cuadrada arr se muestra a continuación:

1 2 3
4 5 6
9 8 9

La diagonal de izquierda a derecha es 1 + 5 + 9 = 15. La diagonal de derecha a izquierda es 3 + 5 + 9 = 17. Su diferencia absoluta es |15 - 17| = 2.

Explicación y Solución:

Aquí trabajamos con un “slice de slices” ([][]int32) para representar la matriz. Necesitamos dos sumas: una para cada diagonal.

  • Diagonal principal (izquierda a derecha): Los elementos tienen el mismo índice de fila y columna (matriz[i][i]).
  • Diagonal secundaria (derecha a izquierda): La suma de los índices de fila y columna siempre es N-1 (matriz[i][N-1-i]).

Al final, restamos las sumas y usamos una función para obtener el valor absoluto.

package main
import "fmt"
/*
* diagonalDifference calcula la diferencia absoluta entre las sumas
* de las diagonales de una matriz cuadrada.
*/
func diagonalDifference(arr [][]int32) int32 {
var sumaPrincipal int32 = 0
var sumaSecundaria int32 = 0
n := len(arr) // Obtenemos el tamaño de la matriz (N)
for i := 0; i < n; i++ {
// Sumamos el elemento de la diagonal principal
sumaPrincipal += arr[i][i]
// Sumamos el elemento de la diagonal secundaria
sumaSecundaria += arr[i][n-1-i]
}
// Calculamos la diferencia
diferencia := sumaPrincipal - sumaSecundaria
// Si la diferencia es negativa, la convertimos a positiva (valor absoluto)
if diferencia < 0 {
diferencia = -diferencia
}
return diferencia
}
func main() {
matriz := [][]int32{
{11, 2, 4},
{4, 5, 6},
{10, 8, -12},
}
resultado := diagonalDifference(matriz)
fmt.Println("La diferencia absoluta de las diagonales es:", resultado) // Salida: La diferencia absoluta de las diagonales es: 15
}

Puntos clave:

  • Slice de slices ([][]int32): Así representamos una matriz o tabla en Go.

  • Lógica de índices: Entender cómo acceder a arr[i][i] y arr[i][n-1-i] es el corazón del problema.

  • Valor absoluto: Un simple if nos ayuda a asegurar que el resultado nunca sea negativo.


5. Plus Minus

Enunciado:

Dado un arreglo de enteros, calcula las proporciones de sus elementos que son positivos, negativos y cero. Imprime los valores decimales de cada fracción en una nueva línea con 6 lugares después del decimal.

Explicación y Solución:

Este ejercicio se trata de contar. Recorremos el slice una sola vez y usamos tres contadores: positivos, negativos y ceros. Después de contar, dividimos cada contador por el número total de elementos para obtener la proporción.

Ojo! Para que la división nos dé un decimal (float64), tenemos que convertir los contadores y el total a float64 antes de dividir. Si no, Go haría una división de enteros y el resultado sería 0.

package main
import "fmt"
/*
* plusMinus cuenta la proporción de números positivos, negativos y ceros
* en un slice y los imprime con 6 decimales.
*/
func plusMinus(arr []int32) {
var positivos, negativos, ceros float64
total := float64(len(arr)) // El total de elementos, convertido a float64
for _, valor := range arr {
if valor > 0 {
positivos++
} else if valor < 0 {
negativos++
} else {
ceros++
}
}
// Imprimimos cada proporción con el formato requerido.
// '%.6f' significa: imprime un float con 6 cifras decimales.
// '\n' es un salto de línea.
fmt.Printf("%.6f\n", positivos/total)
fmt.Printf("%.6f\n", negativos/total)
fmt.Printf("%.6f\n", ceros/total)
}
func main() {
numeros := []int32{-4, 3, -9, 0, 4, 1}
plusMinus(numeros)
/*
* Salida:
* 0.500000
* 0.333333
* 0.166667
*/
}

Puntos clave:

  • if/else if/else: Ideal para clasificar los números en tres categorías.

  • Conversión de tipos (float64()): Para obtener un resultado decimal en la división, los números involucrados deben ser de tipo float.

  • Formato de impresión (fmt.Printf): Nos da control total sobre cómo se muestra la salida, incluyendo el número de decimales.


6. Birthday Cake Candles

Enunciado:

Se te encarga la tarea de contar cuántas velas son las más altas. Tienes un arreglo de enteros que representan la altura de las velas. Tu función debe devolver el número de velas que tienen la altura máxima.

Explicación y Solución:

Este problema se resuelve en dos pasadas (o en una si eres astuto).

  1. Encontrar la altura máxima: Recorremos el slice una vez para encontrar cuál es la vela más alta.
  2. Contar las más altas: Recorremos el slice de nuevo y contamos cuántas velas tienen esa altura máxima que encontramos.

Podemos optimizarlo y hacerlo en una sola pasada. Mantenemos una variable para la altura máxima encontrada hasta ahora y otra para el contador. Si encontramos una vela más alta, reiniciamos el contador a 1 y actualizamos la altura máxima. Si encontramos una vela igual a la máxima actual, simplemente incrementamos el contador.

package main
import "fmt"
/*
* birthdayCakeCandles cuenta cuántas velas tienen la altura máxima.
*/
func birthdayCakeCandles(candles []int32) int32 {
var alturaMaxima int32 = 0
var contador int32 = 0
for _, altura := range candles {
if altura > alturaMaxima {
// ¡Encontramos una nueva vela más alta!
// Actualizamos la altura máxima y reiniciamos el contador a 1.
alturaMaxima = altura
contador = 1
} else if altura == alturaMaxima {
// Encontramos otra vela con la altura máxima actual.
// Solo incrementamos el contador.
contador++
}
}
return contador
}
func main() {
velas := []int32{3, 2, 1, 3}
resultado := birthdayCakeCandles(velas)
fmt.Println("Número de velas más altas:", resultado) // Salida: Número de velas más altas: 2
}

Puntos clave:

  • Lógica de una pasada: Combinar la búsqueda del máximo y el conteo es más eficiente.

  • if/else if: Nos permite diferenciar entre encontrar un nuevo máximo y encontrar uno igual al actual.


7. Mini-Max Sum

Enunciado:

Dado un arreglo de cinco enteros positivos, encuentra los valores mínimo y máximo que se pueden calcular sumando exactamente cuatro de los cinco enteros. Luego imprime los respectivos valores mínimo y máximo como una sola línea de dos enteros largos separados por un espacio.

Explicación y Solución:

La forma más sencilla de pensar en esto es:

  • La suma mínima se obtiene sumando todos los números excepto el más grande.
  • La suma máxima se obtiene sumando todos los números excepto el más pequeño.

Entonces, el plan es:

  1. Calcular la suma total de los cinco números.
  2. Encontrar el número más pequeño y el más grande del arreglo.
  3. Suma mínima = Suma total - número más grande.
  4. Suma máxima = Suma total - número más pequeño.
package main
import "fmt"
/*
* miniMaxSum calcula la suma mínima y máxima de 4 de 5 números.
*/
func miniMaxSum(arr []int32) {
// Usamos int64 para las sumas para evitar desbordamientos.
var sumaTotal int64 = 0
// Inicializamos min y max con el primer elemento.
min := arr[0]
max := arr[0]
for _, valor := range arr {
sumaTotal += int64(valor) // Acumulamos la suma total
if valor < min {
min = valor // Encontramos un nuevo mínimo
}
if valor > max {
max = valor // Encontramos un nuevo máximo
}
}
sumaMinima := sumaTotal - int64(max)
sumaMaxima := sumaTotal - int64(min)
fmt.Println(sumaMinima, sumaMaxima)
}
func main() {
numeros := []int32{1, 2, 3, 4, 5}
miniMaxSum(numeros) // Salida: 10 14
}

Puntos clave:

  • Enfoque Lógico: Darse cuenta de que solo necesitas restar el máximo/mínimo de la suma total simplifica mucho el problema.

  • Inicialización de min y max: Es una práctica común inicializarlos con el primer elemento del arreglo para tener un punto de partida para las comparaciones.


8. FizzBuzz (Variación común)

Enunciado:

Escribe un programa que imprima los números del 1 al 100. Pero para los múltiplos de tres, imprime “Fizz” en lugar del número y para los múltiplos de cinco, imprime “Buzz”. Para los números que son múltiplos de tres y cinco, imprime “FizzBuzz”.

Explicación y Solución:

Este es un clásico. La clave está en el orden de las comprobaciones. Debes verificar primero la condición más específica, que es “múltiplo de ambos (3 y 5)”. Si lo haces al revés, un número como 15 sería atrapado por la condición “múltiplo de 3” y nunca llegarías a comprobar si también es múltiplo de 5.

El operador módulo (%) es tu mejor amigo aquí. numero % 3 == 0 significa que numero es divisible exactamente por 3.

package main
import "fmt"
/*
* fizzBuzz es un ejercicio clásico de lógica de programación.
*/
func fizzBuzz() {
// Bucle del 1 al 100
for i := 1; i <= 100; i++ {
// ¿Es múltiplo de 3 Y de 5? Esta es la condición más específica.
if i%3 == 0 && i%5 == 0 {
fmt.Println("FizzBuzz")
} else if i%3 == 0 { // ¿Es solo múltiplo de 3?
fmt.Println("Fizz")
} else if i%5 == 0 { // ¿Es solo múltiplo de 5?
fmt.Println("Buzz")
} else { // Si no es múltiplo de ninguno
fmt.Println(i)
}
}
}
func main() {
fizzBuzz()
}

Puntos clave:

  • Orden de los if: ¡Fundamental! De lo más específico a lo más general.

  • Operador Módulo (%): Esencial para comprobar la divisibilidad.

  • Operador Lógico AND (&&): Para comprobar que ambas condiciones (%3==0 y %5==0) son verdaderas al mismo tiempo.


9. Contar Frecuencia de Palabras (Uso de Mapas)

Enunciado:

Dado un texto (una cadena de caracteres), cuenta la frecuencia de cada palabra. Devuelve un mapa donde la clave es la palabra y el valor es el número de veces que aparece. Ignora mayúsculas y minúsculas (trata “Hola” y “hola” como la misma palabra).

Explicación y Solución:

¡Aquí es donde los mapas brillan! Un mapa es una colección de pares clave-valor. Es perfecto para asociar una palabra (la clave) con su conteo (el valor).

El plan:

  1. Convertir todo el texto a minúsculas.
  2. Dividir el texto en palabras individuales (esto nos da un slice de strings).
  3. Crear un mapa vacío.
  4. Recorrer el slice de palabras. Para cada palabra:
    • Verificar si ya existe como clave en el mapa.
    • Si existe, incrementar su valor (el contador).
    • Si no existe, agregarla al mapa con un valor de 1.

Go hace esto último súper fácil. Si intentas acceder a una clave que no existe, te devuelve el “valor cero” para ese tipo (en este caso, 0 para un int), así que puedes incrementar directamente.

package main
import (
"fmt"
"strings"
)
/*
* contarFrecuenciaPalabras usa un mapa para contar cuántas veces
* aparece cada palabra en un texto.
*/
func contarFrecuenciaPalabras(texto string) map[string]int {
// 1. Convertimos todo a minúsculas para no distinguir entre mayúsculas y minúsculas.
textoEnMinusculas := strings.ToLower(texto)
// 2. Dividimos el string en un slice de palabras usando el espacio como separador.
palabras := strings.Fields(textoEnMinusculas)
// 3. Creamos un mapa para almacenar la frecuencia.
// La clave será la palabra (string) y el valor su conteo (int).
frecuencia := make(map[string]int)
// 4. Recorremos el slice de palabras.
for _, palabra := range palabras {
// Esta es la magia de los mapas en Go:
// Si la 'palabra' ya es una clave, incrementa su valor.
// Si no existe, la crea y la inicializa en 1 (0 + 1).
frecuencia[palabra]++
}
return frecuencia
}
func main() {
frase := "Hola mi pana todo bien todo fino mi pana"
resultado := contarFrecuenciaPalabras(frase)
fmt.Println(resultado) // Salida: map[bien:1 hola:1 mi:2 pana:2 todo:2 fino:1]
}

Puntos clave:

  • Mapa (map[string]int): La estructura de datos ideal para este problema. make(map[string]int) es como se crea un mapa vacío.

  • Librería strings: Nos provee funciones útiles como ToLower (convertir a minúsculas) y Fields (dividir por espacios).

  • Incremento directo (frecuencia[palabra]++): La forma idiomática y concisa de Go para contar con mapas.


10. División Segura (Manejo de Errores)

Enunciado:

Crea una función que reciba dos números enteros, dividendo y divisor. La función debe devolver el resultado de la división. Sin embargo, si el divisor es cero, la división no es posible. En ese caso, la función debe devolver un error indicando “no se puede dividir por cero”.

Explicación y Solución:

El manejo de errores es una parte fundamental de escribir código robusto en Go. La convención en Go es que una función que puede fallar devuelva dos valores: el resultado y un error. Si la operación fue exitosa, el error es nil (nulo). Si algo salió mal, se devuelve un valor de error.

El que llama a la función tiene la responsabilidad de revisar si el error es nil o no.

package main
import (
"errors" // Paquete para crear errores personalizados.
"fmt"
)
/*
* divisionSegura divide dos números, pero devuelve un error
* si se intenta dividir por cero.
* Devuelve dos valores: un float64 (el resultado) y un error.
*/
func divisionSegura(dividendo, divisor float64) (float64, error) {
// Verificamos la condición de error primero.
if divisor == 0 {
// Si el divisor es cero, no podemos continuar.
// Devolvemos 0 como resultado (un valor "cero" para float64)
// y un nuevo error con un mensaje claro.
return 0, errors.New("pana, no se puede dividir por cero")
}
// Si todo está bien (el error sería nil), calculamos y devolvemos el resultado.
resultado := dividendo / divisor
return resultado, nil // 'nil' significa "sin error".
}
func main() {
// --- Caso Exitoso ---
resultado, err := divisionSegura(10.0, 2.0)
// El primer paso después de llamar a una función que puede fallar
// es SIEMPRE comprobar el error.
if err != nil {
// Si hay un error, lo manejamos (en este caso, lo imprimimos).
fmt.Println("Ocurrió un error:", err)
} else {
// Si no hay error, usamos el resultado.
fmt.Println("El resultado es:", resultado) // Salida: El resultado es: 5
}
// --- Caso con Error ---
resultado2, err2 := divisionSegura(5.0, 0.0)
if err2 != nil {
fmt.Println("Ocurrió un error:", err2) // Salida: Ocurrió un error: pana, no se puede dividir por cero
} else {
fmt.Println("El resultado es:", resultado2)
}
}

Puntos clave:

  • Retorno múltiple: La firma func(...) (float64, error) indica que la función devuelve dos cosas.

  • error como tipo: error es una interfaz integrada en Go. nil es el valor cero para los errores.

  • Paquete errors: errors.New("...") es la forma más simple de crear un valor de error con un mensaje de texto.

  • Comprobación explícita de errores: El if err != nil { ... } es el patrón más común y esencial en la programación en Go.

Bueno muchachones, hasta el proximo capitulo, activitos mis bros…

Next: Domina las Entradas y Salidas (I/O) en Go: De la Terminal al Manejo de Archivos
Foto Cesar Fernandez

¿Lo rompiste? ¿Lo mejoraste?

Gracias por llegar hasta el final. Escribo estos posts para organizar mis propias ideas y, con suerte, para ahorrarle a alguien más el dolor de cabeza que yo ya pasé. Me encuentras en LinkedIn o puedes ver más de mi trabajo en GitHub.


Serie: Curso Profesional de Go