Despesas

Despesas

Exercício de aprendizagem

Introdução

Em Go, funções são valores de primeira classe. Isso significa que você pode fazer com funções as mesmas coisas que faz com qualquer outro valor: atribuir funções a variáveis, passá-las como argumentos para outras funções ou até retornar funções de outras funções.

Abaixo, criamos duas funções, engGreeting e espGreeting, e as atribuímos à 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!

Valores de função permitem parametrizar funções não só com dados, mas também com comportamento. No exemplo a seguir, passamos comportamento para a função dialog por meio 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 de tipo função não inicializada é nil. Por isso, chamar um valor de função nil causa um panic.

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

Valores de função podem ser comparados com nil. Isso 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 denota o conjunto de todas as funções com a mesma sequência de tipos de parâmetro e a mesma sequência de tipos de resultado. Tipos definidos pelo usuário podem ser declarados com base em tipos de função. Por exemplo, a função dialog dos exemplos anteriores pode ser atualizada assim:

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 existe graças ao suporte a funções de primeira classe são as funções anônimas. Funções anônimas são definidas no ponto em que são usadas, sem um nome depois da palavra-chave func. Essas 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 retorna uma função anônima que, por sua vez, muda os valores dessas variáveis a cada chamada da função. A enésima chamada da função anônima retorna o enésimo número da sequência de Fibonacci a partir de 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 fazer valores de função manterem estado entre chamadas. Dizemos que a função anônima é um closure das variáveis n1 e n2. Closures são muito usados em programação, e você pode encontrar outras linguagens que os suportam.

Instruções

Bob é um consultor financeiro e ajuda as pessoas a gerenciar suas despesas. Os clientes de Bob enviam registros de despesas para ele analisar. Bob tem registros dos períodos anteriores, então consegue ver mudanças nos gastos. 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 de Bob começou sua atividade.

Neste exercício, você vai construir um programa para ajudar Bob a gerenciar e analisar as despesas dos clientes dele.

Bob trabalha com Records e DaysPeriods.

Um Record representa um registro 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é o dia To. As duas extremidades estão incluídas 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. Implemente um filtro geral de registros

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

Implemente a função genérica Filter para filtrar registros de acordo com um critério dado por uma função. Essa função de filtro recebe uma coleção de registros e uma função de predicado e retorna apenas os registros 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. Filtre registros dentro de um período de tempo

Bob frequentemente precisa filtrar registros que estão em um determinado período de tempo.

Implemente a função ByDaysPeriod, que vai ajudar Bob a criar esses filtros. Essa função recebe um DaysPeriod e retorna uma função que recebe um registro e diz se o registro 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. Filtre registros por categoria

Além de filtrar registros por um período de tempo, Bob também precisa filtrar registros pela categoria deles.

Implemente a função ByCategory, que vai ajudar Bob a criar esses filtros. Essa função recebe uma categoria e retorna uma função que recebe um registro e diz se a categoria desse registro é a mesma que a 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. Calcule o valor total das despesas em um período

Bob também quer saber o valor total das despesas em um período de tempo.

Implemente a função TotalByPeriod para retornar 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. Calcule as despesas totais dos registros de uma categoria em um período

Para os relatórios mais complexos que Bob faz para seus clientes, Bob precisa filtrar registros por categoria e período de tempo ao mesmo tempo. Ou seja, Bob quer saber as despesas totais dos registros de uma categoria em um determinado período de tempo.

Implemente a função CategoryExpenses, que retorna o valor total das despesas de uma categoria em um determinado período de dias. A função também deve diferenciar o caso em que a categoria dada não está presente nos registros de despesas do caso em que não há despesas da categoria no período fornecido. Quando a categoria não é categoria de nenhum dos registros (independentemente do período de tempo), a função deve retornar 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 O link abre em uma nova janela ou aba
Go Exercism

Tudo pronto para começar Despesas?

Crie sua conta no Exercism para aprender e dominar Go com 34 conceitos165 exercícios e mentoria humana de verdade, tudo de graça.