Gastos

Gastos

Ejercicio de aprendizaje

Introducción

En Go, las funciones son valores de primera clase. Esto significa que puedes hacer con las funciones las mismas cosas que puedes hacer con cualquier otro valor: asignar funciones a variables, pasarlas como argumentos a otras funciones o incluso devolver funciones desde otras funciones.

A continuación creamos dos funciones, engGreeting y espGreeting, y las asignamos a la variable greeting:

import "fmt"

func engGreeting(name string) string {
	return fmt.Sprintf("Hello %s, nice to meet you!", name)
}

func espGreeting(name string) string {
	return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
}

greeting := engGreeting			// greeting is a variable of type func(string) string
fmt.Println(greeting("Alice"))	// Hello Alice, nice to meet you!

greeting = espGreeting
fmt.Println(greeting("Alice")) 	// ¡Hola Alice, mucho gusto!

Los valores de función dan la oportunidad de parametrizar funciones no solo con datos, sino también con comportamiento. En el siguiente ejemplo, pasamos comportamiento a la función dialog mediante el parámetro greetingFunc:

func dialog(name string, greetingFunc func(string) string) {
	fmt.Println(greetingFunc(name))
	fmt.Println("I'm a dialog bot.")
}

func espGreeting(name string) string {
	return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
}

greeting := espGreeting
dialog("Alice", greeting)
// =>
// ¡Hola Alice, mucho gusto!
// I'm a dialog bot.

El valor de una variable no inicializada de tipo función es nil. Por lo tanto, llamar a un valor de función nil provoca un panic.

var dutchGreeting func(string) string
dutchGreeting("Alice") // panic: call of nil function

Los valores de función se pueden comparar con nil. Esto puede ser útil para evitar panics innecesarios en el programa.

var dutchGreeting func(string) string
if dutchGreeting != nil {
	dutchGreeting("Alice") // safe to call dutchGreeting
}

Tipos de función

Usar valores de función es posible gracias a los tipos de función en Go. Un tipo de función denota el conjunto de todas las funciones con la misma secuencia de tipos de parámetro y la misma secuencia de tipos de resultado. Se pueden declarar tipos definidos por el usuario sobre tipos de función. Por ejemplo, la función dialog de los ejemplos anteriores se puede actualizar de la siguiente manera:

type greetingFunc func(string) string

func dialog(name string, f greetingFunc) {
	fmt.Println(f(name))
	fmt.Println("I'm a dialog bot.")
}

Funciones anónimas

Otra herramienta poderosa que está disponible gracias al soporte de funciones de primera clase son las funciones anónimas. Las funciones anónimas se definen en el punto donde se usan, sin un nombre después de la palabra clave func. Estas funciones tienen acceso a las variables de la función que las contiene.

Por ejemplo:

func fib() func() int {
	var n1, n2 int

	return func() int {
		if n1 == 0 && n2 == 0 {
			n1 = 1
		} else {
			n1, n2 = n2, n1 + n2
		}
		return n2
	}
}

next := fib()
for i := range N {
  fmt.Printf("F%d\t= %4d\n", i, next())
}

Una llamada a fib declara las variables n1 y n2 y devuelve una función anónima que, a su vez, cambia los valores de estas variables cada vez que se llama a la función. La enésima llamada a la función anónima devuelve el enésimo número de la sucesión de Fibonacci, empezando desde 0. La función anónima interna tiene acceso a las variables locales (n1 y n2) de la función contenedora fib. Esta es una gran manera de que los valores de función mantengan su estado entre llamadas. Decimos que la función anónima es un closure de las variables n1 y n2. Los closures se usan ampliamente en programación y es posible que veas otros lenguajes que los admiten.

Instrucciones

Bob es un asesor financiero y ayuda a las personas a gestionar sus gastos. Los clientes de Bob le envían registros de gastos para que los analice. Bob tiene registros de los periodos anteriores, así puede ver los cambios en el gasto. A Bob no le gustan los calendarios y usa Bob epoch en lugar de fechas. Bob epoch es el número de días transcurridos desde que el cliente de Bob comenzó su actividad.

En este ejercicio vas a crear un programa que ayude a Bob a gestionar y analizar los gastos de sus clientes.

Bob trabaja con Records y DaysPeriods.

Un Record representa un registro de gastos que contiene el día en que se hizo el gasto, el dinero gastado y la categoría del gasto.

// Record represents an expense record.
type Record struct {
	Day         int
	Amount      float64
	Category    string
}

Un DaysPeriod representa un rango de días e incluye todos los días desde el día From hasta el día To. Ambos extremos están incluidos en el rango.

// DaysPeriod represents a period of days.
type DaysPeriod struct {
	From int
	To   int
}

p := DaysPeriod{From: 1, To: 31}
// p represents all days from the day 1 to the day 31:
//  - days 1, 20, 16 and 31 are examples of days that are included
//    in the range of time specified by p
//  - days 50 and 40 are examples of days that are not included
//    in the range of time specified by p

1. Implementa un filtro general de registros

Bob maneja muchos registros todos los días, pero no todos son interesantes, depende del análisis que esté haciendo. Ayudemos a Bob a hacer un filtrado básico de registros.

Implementa la función genérica Filter para filtrar registros según un criterio dado por una función. Esta función de filtro acepta una colección de registros y una función predicado, y devuelve solo los registros de la colección que cumplen con el predicado.

records := []Record{
  {Day: 1, Amount: 15, Category: "groceries"},
  {Day: 11, Amount: 300, Category: "utility-bills"},
  {Day: 12, Amount: 28, Category: "groceries"},
}

// Day1Records only returns true for records that are from day 1
func Day1Records(r Record) bool {
  return r.Day == 1
}

Filter(records, Day1Records)
// =>
// [
//   {Day: 1, Amount: 15, Category: "groceries"}
// ]

2. Filtra registros dentro de un periodo de tiempo

Bob con frecuencia necesita filtrar registros que están en un periodo de tiempo dado.

Implementa la función ByDaysPeriod, que ayudará a Bob a crear ese tipo de filtros. Esta función acepta un DaysPeriod y devuelve una función que toma un registro y dice si el registro está en el periodo de tiempo especificado por el DaysPeriod que se pasa como argumento.

records := []Record{
  {Day: 1, Amount: 15, Category: "groceries"},
  {Day: 11, Amount: 300, Category: "utility-bills"},
  {Day: 12, Amount: 28, Category: "groceries"},
  {Day: 26, Amount: 300, Category: "university"},
  {Day: 28, Amount: 1300, Category: "rent"},
}

period := DaysPeriod{From: 1, To: 15}

Filter(records, ByDaysPeriod(period))
// =>
// [
//   {Day: 1, Amount: 15, Category: "groceries"},
//   {Day: 11, Amount: 300, Category: "utility-bills"},
//   {Day: 12, Amount: 28, Category: "groceries"},
// ]

3. Filtra registros por categoría

Además de filtrar registros por un periodo de tiempo, Bob también necesita filtrar registros por su categoría.

Implementa la función ByCategory, que ayudará a Bob a crear ese tipo de filtros. Esta función acepta una categoría y devuelve una función que toma un registro y dice si la categoría de ese registro es la misma que la categoría que se pasa como argumento.

records := []Record{
  {Day: 1, Amount: 15, Category: "groceries"},
  {Day: 11, Amount: 300, Category: "utility-bills"},
  {Day: 12, Amount: 28, Category: "groceries"},
  {Day: 28, Amount: 1300, Category: "rent"},
}

Filter(records, ByCategory("groceries"))
// =>
// [
//   {Day: 1, Amount: 15, Category: "groceries"},
//   {Day: 12, Amount: 28, Category: "groceries"},
// ]

4. Calcula el monto total de los gastos en un periodo

Bob también quiere saber el monto total de los gastos en un periodo de tiempo.

Implementa la función TotalByPeriod para devolver la suma de los gastos en el periodo de días.

records := []Record{
  {Day: 15, Amount: 16, Category: "entertainment"},
  {Day: 32, Amount: 20, Category: "groceries"},
  {Day: 40, Amount: 30, Category: "entertainment"}
}

p1 := DaysPeriod{From: 1, To: 30}
p2 := DaysPeriod{From: 31, To: 60}

TotalByPeriod(records, p1)
// => 16

TotalByPeriod(records, p2)
// => 50

5. Calcula el total de gastos de los registros de una categoría en un periodo

Para los informes más complejos que Bob hace a sus clientes, Bob necesita filtrar registros por categoría y por periodo de tiempo a la vez. Es decir, Bob quiere saber el total de gastos de los registros de una categoría en un periodo de tiempo dado.

Implementa la función CategoryExpenses, que devuelve el monto total de los gastos de una categoría en un periodo de días dado. La función también debe diferenciar el caso en que la categoría dada no está presente en los registros de gastos y el caso en que no hay gastos de esa categoría en el periodo proporcionado. Cuando la categoría no es la categoría de ninguno de los registros (sin importar el periodo de tiempo), la función debe devolver un error.

p1 := DaysPeriod{From: 1, To: 30}
p2 := DaysPeriod{From: 31, To: 60}

records := []Record{
  {Day: 1, Amount: 15, Category: "groceries"},
  {Day: 11, Amount: 300, Category: "utility-bills"},
  {Day: 12, Amount: 28, Category: "groceries"},
  {Day: 26, Amount: 300, Category: "university"},
  {Day: 28, Amount: 1300, Category: "rent"},
}

CategoryExpenses(records, p1, "entertainment")
// => 0, error(unknown category entertainment)

CategoryExpenses(records, p1, "rent")
// => 1300, nil

CategoryExpenses(records, p2, "rent")
// => 0, nil
Editar en GitHub El enlace se abre en una ventana o una pestaña nuevas
Go Exercism

¿Todo listo para empezar Gastos?

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