Rutas
/
Go
Go
/
Ejercicios
/
Día de las elecciones
Día de las elecciones

Día de las elecciones

Ejercicio de aprendizaje

Introducción

Como muchos otros lenguajes, Go tiene punteros. Si eres nuevo en el uso de punteros, pueden parecer un poco misteriosos, pero una vez que te acostumbras a ellos, son bastante sencillos. Son una parte crucial de Go, así que tómate un tiempo para entenderlos de verdad.

Antes de profundizar en los detalles, conviene entender el uso de los punteros. Los punteros son una forma de compartir memoria con otras partes de nuestro programa, lo cual es útil por dos razones principales:

  1. Cuando tenemos grandes cantidades de datos, hacer copias para pasarlas entre funciones es muy ineficiente. Al pasar en su lugar la ubicación de memoria donde se almacenan los datos, podemos reducir drásticamente el consumo de recursos de nuestros programas.
  2. Al pasar punteros entre funciones, podemos acceder y modificar directamente la única copia de los datos, lo que significa que cualquier cambio realizado por una función es visible de inmediato para otras partes del programa cuando la función termina.

Variables y memoria

Supongamos que tenemos una variable entera normal a:

var a int

Cuando declaramos una variable, Go tiene que encontrar un lugar en la memoria para almacenar su valor. Esto está en gran medida abstraído de nosotros: cuando necesitamos obtener el valor almacenado en esa parte de la memoria, podemos referirnos a él simplemente por el nombre de la variable.

Por ejemplo, cuando escribimos a + 2, en realidad estamos obteniendo el valor almacenado en la memoria asociada a la variable a y sumándole 2.

De forma similar, cuando necesitamos cambiar el valor en la parte de memoria de a, podemos usar el nombre de la variable para hacer una asignación:

a = 3

La parte de memoria asociada a a ahora almacenará el valor 3.

Punteros

Aunque las variables nos permiten referirnos a valores en memoria, a veces es útil conocer la dirección de memoria a la que apunta la variable. Los punteros contienen las direcciones de memoria de esos valores. Declaras una variable con un tipo puntero anteponiendo un asterisco al tipo subyacente:

var p *int // 'p' contains the memory address of an integer

Aquí declaramos una variable p de tipo «puntero a int» (*int). Esto significa que p contendrá la dirección de memoria de un entero. El valor cero de los punteros es nil porque un puntero nil no contiene ninguna dirección de memoria.

Obtener un puntero a una variable

Para encontrar la dirección de memoria del valor de una variable, podemos usar el operador &. Por ejemplo, si queremos encontrar y almacenar la dirección de memoria de la variable a en el puntero p, podemos hacer lo siguiente:

var a int
a = 2

var p *int
p = &a // the variable 'p' contains the memory address of 'a'

Acceder al valor a través de un puntero (desreferenciación)

Cuando tenemos un puntero, puede que queramos conocer el valor almacenado en la dirección de memoria que representa el puntero. Podemos hacerlo usando el operador *:

var a int
a = 2

var p *int
p = &a // the variable 'p' contains the memory address of 'a'

var b int
b = *p // b == 2

La operación *p obtiene el valor almacenado en la dirección de memoria que contiene p. Esta operación suele llamarse «desreferenciación».

También podemos usar el operador de desreferencia para asignar un nuevo valor a la dirección de memoria a la que hace referencia el puntero:

var a int        // declare int variable 'a'
a = 2            // assign 'a' the value of 2

var pa *int
pa = &a          // 'pa' now contains to the memory address of 'a'
*pa = *pa + 2    // increment by 2 the value at memory address 'pa'

fmt.Println(a)   // Output: 4
                 // 'a' will have the new value that was changed via the pointer!

Asignar a *pa cambiará el valor almacenado en la dirección de memoria que contiene pa. Como pa contiene la dirección de memoria de a, al asignar a *pa estamos cambiando el valor de a.

No obstante, una advertencia: comprueba siempre que un puntero no sea nil antes de desreferenciarlo. Desreferenciar un puntero nil hará que el programa se bloquee en tiempo de ejecución.

var p *int // p is nil initially
fmt.Println(*p)
// panic: runtime error: invalid memory address or nil pointer dereference

Punteros a estructuras

Hasta ahora solo hemos visto punteros a valores primitivos. También podemos crear punteros para estructuras:

type Person struct {
    Name string
    Age  int
}

var peter Person
peter = Person{Name: "Peter", Age: 22}

var p *Person
p = &peter

También podríamos haber creado un nuevo Person y almacenado inmediatamente un puntero a él:

var p *Person
p = &Person{Name: "Peter", Age: 22}

Cuando tenemos un puntero a una estructura, no necesitamos desreferenciar el puntero antes de acceder a uno de los campos:

var p *Person
p = &Person{Name: "Peter", Age: 22}

fmt.Println(p.Name) // Output: "Peter"
                    // Go automatically dereferences 'p' to allow
                    // access to the 'Name' field

Los slices y los mapas ya son punteros

Los slices y los mapas son tipos especiales porque ya tienen punteros en su implementación. Esto significa que, la mayoría de las veces, no necesitamos crear punteros para estos tipos para compartir la dirección de memoria de sus valores. Imagina que tenemos una función que incrementa el valor de una clave en un mapa:

func incrementPeterAge(m map[string]int) {
	m["Peter"] += 1
}

Si creamos un mapa y llamamos a esta función, los cambios que la función hizo en el mapa persisten después de que la función termina. Este es un comportamiento similar al que obtenemos si usáramos un puntero, pero fíjate en que en este ejemplo no estamos usando ninguna referencia/desreferencia ni nada de la sintaxis de punteros:

ages := map[string]int{
  "Peter": 21
}
incrementPeterAge(ages)
fmt.Println(ages)
// Output: map[Peter:22]
// The changes the function 'incrementPeterAge' made to the map are visible after the function ends!

Lo mismo se aplica cuando cambiamos un elemento existente en un slice.

Sin embargo, las acciones que devuelven un nuevo slice como append son un caso especial y puede que no modifiquen el slice fuera de la función. Esto se debe a la forma en que los slices funcionan internamente, pero no cubriremos esto en detalle en este ejercicio, ya que es un tema más avanzado. Si tienes mucha curiosidad, puedes leer más sobre esto en Go Blog: Mechanics of 'append'

Instrucciones

Una escuela local cerca de ti tiene una asociación de estudiantes muy activa. La asociación de estudiantes está gestionada por un presidente y, cada 2 años, se celebran elecciones para elegir a un nuevo presidente.

En las elecciones de este año, se decidió que se necesitaba un nuevo sistema digital para contar los votos. La escuela necesita tu ayuda para construir este nuevo sistema.

1. Crea un contador de votos

Una de las primeras cosas que necesita el nuevo sistema de votación es un contador de votos. Este contador es una forma de llevar la cuenta de los votos que tiene un candidato concreto.

Crea una función NewVoteCounter que acepte el número de votos iniciales de un candidato y devuelva un puntero que hace referencia a un int, inicializado con el número de votos iniciales dado.

var initialVotes int
initialVotes = 2

var counter *int
counter = NewVoteCounter(initialVotes)
*counter == initialVotes // true

2. Obtén el número de votos de un contador

¡Ya tienes una forma de crear contadores nuevos! Pero ahora te das cuenta de que el nuevo sistema también necesitará una forma de obtener el número de votos de un contador.

Crea una función VoteCount que tome un contador (*int) como argumento y devuelva el número de votos del contador. Si el contador es nil, debes suponer que no tiene votos:

var votes int
votes = 3

var voteCounter *int
voteCounter = &votes

VoteCount(voteCounter)
// => 3

var nilVoteCounter *int
VoteCount(nilVoteCounter)
// => 0

3. Incrementa los votos de un contador

¡Por fin es el momento de contar los votos! Ahora necesitas una forma de incrementar los votos de un contador.

Crea una función IncrementVoteCount que tome un contador (*int) como argumento y un número de votos, e incremente el contador en ese número de votos. Puedes suponer que el puntero que se pasa nunca será nil.

var votes int
votes = 3

var voteCounter *int
voteCounter = &votes

IncrementVoteCount(voteCounter, 2)

votes == 5          // true
*voteCounter == 5   // true

4. Crea los resultados de las elecciones

Con todos los votos ya contados, es el momento de preparar el anuncio de los resultados para toda la escuela. Para ello, te das cuenta de que tener solo contadores de votos es insuficiente. Hace falta una forma de asociar el número de votos con un candidato concreto.

Crea una función NewElectionResult que reciba el nombre de un candidato y su número de votos y devuelva un nuevo resultado electoral.

var result *ElectionResult
result = NewElectionResult("Peter", 3)

result.Name == "Peter"  // true
result.Votes == 3       // true

La estructura del resultado electoral ya está creada para ti y se define así:

type ElectionResult struct {
    // Name of the candidate
    Name    string
    // Votes of votes the candidate had
    Votes   int
}

5. Anuncia los resultados

¡Es el momento de anunciar al nuevo presidente a la escuela! El presidente se anunciará en los pequeños paneles digitales de mensajes que tiene la escuela. El mensaje debe mostrar el nombre del nuevo presidente y los votos que obtuvo, con el siguiente formato: <candidate_name> (<votes>). Un ejemplo de ese mensaje es: "Peter (51)".

Crea una función DisplayResult que reciba un *ElectionResult como argumento y devuelva un string con el mensaje que se debe mostrar.

var result *ElectionResult
result = &ElectionResult{
    Name: "John",
    Votes: 32,
}

DisplayResult(result)
// => John (32)

6. Recuento de votos

Para asegurarse de que los resultados finales eran correctos, se volvieron a contar los votos. En el recuento, se descubrió que el número de votos de algunos candidatos difería en uno.

Crea una función DecrementVotesOfCandidate que reciba los resultados finales y el nombre de un candidato cuyo número de votos debes decrementar. Los resultados finales se dan en forma de un map[string]int, donde las claves son los nombres de los candidatos y los valores son sus votos totales.

var finalResults = map[string]int{
    "Mary":  10,
    "John":  51,
}

DecrementVotesOfCandidate(finalResults, "Mary")

finalResults["Mary"]
// => 9
Editar en GitHub El enlace se abre en una ventana o pestaña nueva
Go Exercism

¿Listo para empezar Día de las elecciones?

Regístrate en Exercism para aprender y dominar Go con 34 conceptos165 ejercicios y mentoría humana real, todo gratis.