Despesas

Despesas

Exercício de aprendizagem

Introdução

Em Go, as funções são valores de primeira classe. Isto significa que podes fazer com as funções as mesmas coisas que fazes com todos os outros valores: atribuir funções a variáveis, passá-las como argumentos a outras funções ou até devolver funções de outras funções.

Abaixo, criamos duas funções, engGreeting e espGreeting, e atribuímo-las à variável 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!

Os valores de função dão a oportunidade de parametrizar funções não só com dados, mas também com comportamento. No exemplo seguinte, passamos comportamento à função dialog através do 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.

O valor de uma variável não inicializada de tipo função é nil. Por isso, chamar um valor de função nil provoca um panic.

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

Os valores de função podem ser comparados com nil. Isto pode ser útil para evitar panics desnecessários no programa.

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

Tipos de função

Usar valores de função é possível graças aos tipos de função em Go. Um tipo de função designa o conjunto de todas as funções com a mesma sequência de tipos de parâmetros e a mesma sequência de tipos de resultado. É possível declarar tipos definidos pelo utilizador sobre tipos de função. Por exemplo, a função dialog dos exemplos anteriores pode ser atualizada da seguinte forma:

type greetingFunc func(string) string

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

Funções anónimas

Outra ferramenta poderosa que está disponível graças ao suporte de funções de primeira classe são as funções anónimas. As funções anónimas são definidas no ponto em que são usadas, sem um nome a seguir à palavra-chave func. Estas funções têm acesso às variáveis da função que as envolve.

Por exemplo:

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())
}

Uma chamada a fib declara as variáveis n1 e n2 e devolve uma função anónima que, por sua vez, altera os valores destas variáveis cada vez que a função é chamada. A enésima chamada da função anónima devolve o enésimo número da sequência de Fibonacci, começando em 0. A função anónima interna tem acesso às variáveis locais (n1 e n2) da função fib que a envolve. É uma ótima forma de os valores de função manterem estado entre chamadas. Dizemos que a função anónima é uma closure das variáveis n1 e n2. Os closures são muito usados em programação e é possível que vejas outras linguagens a suportá-los.

Instruções

O Bob é consultor financeiro e ajuda as pessoas a gerir as suas despesas. Os clientes do Bob enviam-lhe registos de despesas para ele analisar. O Bob tem registos dos períodos anteriores, por isso consegue ver as alterações nos gastos. O Bob não gosta de calendários e usa a Bob epoch em vez de datas. A Bob epoch é o número de dias decorridos desde que o cliente do Bob começou a sua atividade.

Neste exercício, vais criar um programa para ajudar o Bob a gerir e a analisar as despesas dos seus clientes.

O Bob trabalha com Records e DaysPeriods.

Um Record representa um registo de despesa que contém o dia em que a despesa foi feita, o dinheiro gasto e a categoria da despesa.

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

Um DaysPeriod representa um intervalo de dias e inclui todos os dias desde o dia From até ao dia To. Ambos os extremos estão incluídos no intervalo.

// 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 um filtro geral de registos

O Bob lida com muitos registos todos os dias, mas nem todos são interessantes, dependendo da análise que o Bob está a fazer. Vamos ajudar o Bob a fazer uma filtragem básica de registos.

Implementa a função genérica Filter para filtrar registos de acordo com um critério dado por uma função. Esta função de filtragem aceita uma coleção de registos e uma função de predicado e devolve apenas os registos da coleção que satisfazem o 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 registos dentro de um período de tempo

O Bob precisa frequentemente de filtrar registos que estão num determinado período de tempo.

Implementa a função ByDaysPeriod que vai ajudar o Bob a criar esses filtros. Esta função aceita um DaysPeriod e devolve uma função que recebe um registo e indica se o registo está no período de tempo especificado pelo DaysPeriod dado 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 registos por categoria

Além de filtrar registos por um período de tempo, o Bob também precisa de filtrar registos pela sua categoria.

Implementa a função ByCategory que vai ajudar o Bob a criar esses filtros. Esta função aceita uma categoria e devolve uma função que recebe um registo e indica se a categoria deste registo é igual à categoria dada 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 o montante total das despesas num período

O Bob também quer saber o montante total das despesas num período de tempo.

Implementa a função TotalByPeriod para devolver a soma das despesas no período de dias.

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 o total de despesas dos registos de uma categoria num período

Para os relatórios mais complexos que o Bob faz aos seus clientes, o Bob precisa de filtrar registos por categoria e período de tempo ao mesmo tempo. Isto significa que o Bob quer saber o total de despesas dos registos de uma categoria num determinado período de tempo.

Implementa a função CategoryExpenses que devolve o montante total das despesas de uma categoria num determinado período de dias. A função deve também distinguir o caso em que a categoria dada não está presente nos registos de despesas do caso em que não há despesas da categoria no período indicado. Quando a categoria não é a categoria de nenhum dos registos (independentemente do período de tempo), a função deve devolver um erro.

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 via GitHub A ligação abre numa nova janela ou separador
Go Exercism

Estás pronto para começar Despesas?

Inscreve-te no Exercism para aprenderes e dominares Go com 34 conceitos165 exercícios, e mentoria humana real, tudo grátis.