Tracks
/
Go
Go
/
Übungen
/
Ausgaben
Ausgaben

Ausgaben

Lernübung

Einführung

In Go sind Funktionen erstklassige Werte. Das bedeutet, dass du mit Funktionen dieselben Dinge tun kannst wie mit allen anderen Werten: Du kannst Funktionen Variablen zuweisen, sie als Argumente an andere Funktionen übergeben oder sogar Funktionen aus anderen Funktionen zurückgeben.

Im Folgenden erstellen wir zwei Funktionen, engGreeting und espGreeting, und weisen sie der Variable greeting zu:

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!

Funktionswerte bieten die Möglichkeit, Funktionen nicht nur mit Daten, sondern auch mit Verhalten zu parametrisieren. Im folgenden Beispiel übergeben wir Verhalten über den Parameter greetingFunc an die Funktion dialog:

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.

Der Wert einer nicht initialisierten Variable vom Funktionstyp ist nil. Daher führt der Aufruf eines nil-Funktionswerts zu einer Panik.

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

Funktionswerte können mit nil verglichen werden. Das kann nützlich sein, um unnötige Paniken im Programm zu vermeiden.

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

Funktionstypen

Die Verwendung von Funktionswerten ist dank der Funktionstypen in Go möglich. Ein Funktionstyp bezeichnet die Menge aller Funktionen mit derselben Abfolge von Parametertypen und derselben Abfolge von Ergebnistypen. Benutzerdefinierte Typen können auf Grundlage von Funktionstypen deklariert werden. Zum Beispiel kann die Funktion dialog aus den vorherigen Beispielen wie folgt aktualisiert werden:

type greetingFunc func(string) string

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

Anonyme Funktionen

Ein weiteres mächtiges Werkzeug, das dank der Unterstützung erstklassiger Funktionen zur Verfügung steht, sind anonyme Funktionen. Anonyme Funktionen werden an ihrer Verwendungsstelle definiert, ohne dass ein Name auf das Schlüsselwort func folgt. Solche Funktionen haben Zugriff auf die Variablen der umgebenden Funktion.

Zum Beispiel:

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

Ein Aufruf von fib deklariert die Variablen n1 und n2 und gibt eine anonyme Funktion zurück, die wiederum bei jedem Aufruf der Funktion die Werte dieser Variablen ändert. Der n-te Aufruf der anonymen Funktion gibt die n-te Zahl der Fibonacci-Folge zurück, beginnend bei 0. Die anonyme innere Funktion hat Zugriff auf die lokalen Variablen (n1 und n2) der umgebenden Funktion fib. Das ist eine großartige Möglichkeit, mit der Funktionswerte ihren Zustand zwischen Aufrufen behalten können. Wir sagen, dass die anonyme Funktion eine Closure der Variablen n1 und n2 ist. Closures werden in der Programmierung häufig verwendet, und du wirst vielleicht sehen, dass andere Sprachen sie unterstützen.

Anleitung

Bob ist Finanzberater und hilft Menschen, ihre Ausgaben zu verwalten. Bobs Kunden schicken ihm Ausgabenaufzeichnungen, die er analysieren soll. Bob hat Aufzeichnungen aus früheren Zeiträumen, damit er Veränderungen bei den Ausgaben erkennen kann. Bob mag keine Kalender und verwendet statt Datumsangaben die Bob-Epoche. Die Bob-Epoche ist die Anzahl der Tage, die vergangen sind, seit Bobs Kunde seine Aktivität begonnen hat.

In dieser Übung baust du ein Programm, das Bob dabei hilft, die Ausgaben seiner Kunden zu verwalten und zu analysieren.

Bob arbeitet mit Records und DaysPeriods.

Ein Record steht für eine Ausgabenaufzeichnung, die den Tag, an dem die Ausgabe gemacht wurde, den ausgegebenen Betrag und die Kategorie der Ausgabe enthält.

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

Ein DaysPeriod steht für einen Zeitraum von Tagen und umfasst alle Tage vom Tag From bis zum Tag To. Beide Enden sind im Zeitraum enthalten.

// 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. Implementiere einen allgemeinen Filter für Aufzeichnungen

Bob hat jeden Tag mit vielen Aufzeichnungen zu tun, aber je nach Analyse sind nicht alle davon interessant. Helfen wir Bob dabei, eine einfache Filterung der Aufzeichnungen durchzuführen.

Implementiere die generische Funktion Filter, um Aufzeichnungen nach einem Kriterium zu filtern, das durch eine Funktion vorgegeben wird. Diese Filterfunktion nimmt eine Sammlung von Aufzeichnungen und eine Prädikatfunktion entgegen und gibt nur die Aufzeichnungen aus der Sammlung zurück, die das Prädikat erfüllen.

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. Filtere Aufzeichnungen innerhalb eines Zeitraums

Bob muss häufig Aufzeichnungen filtern, die in einem bestimmten Zeitraum liegen.

Implementiere die Funktion ByDaysPeriod, die Bob dabei hilft, solche Filter zu erstellen. Diese Funktion nimmt ein DaysPeriod entgegen und gibt eine Funktion zurück, die eine Aufzeichnung entgegennimmt und angibt, ob die Aufzeichnung in dem Zeitraum liegt, der durch das als Argument übergebene DaysPeriod festgelegt ist.

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. Filtere Aufzeichnungen nach Kategorie

Neben dem Filtern von Aufzeichnungen nach einem Zeitraum muss Bob Aufzeichnungen auch nach ihrer Kategorie filtern.

Implementiere die Funktion ByCategory, die Bob dabei hilft, solche Filter zu erstellen. Diese Funktion nimmt eine Kategorie entgegen und gibt eine Funktion zurück, die eine Aufzeichnung entgegennimmt und angibt, ob die Kategorie dieser Aufzeichnung mit der als Argument übergebenen Kategorie übereinstimmt.

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. Berechne die Gesamtausgaben in einem Zeitraum

Bob möchte außerdem die Gesamtausgaben in einem Zeitraum wissen.

Implementiere die Funktion TotalByPeriod, die die Summe der Ausgaben im Tageszeitraum zurückgibt.

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. Berechne die Gesamtausgaben für Aufzeichnungen einer Kategorie in einem Zeitraum

Für die komplexesten Berichte, die Bob für seine Kunden erstellt, muss Bob Aufzeichnungen gleichzeitig nach Kategorie und Zeitraum filtern. Das heißt, Bob möchte die Gesamtausgaben für Aufzeichnungen einer Kategorie in einem bestimmten Zeitraum wissen.

Implementiere die Funktion CategoryExpenses, die die Gesamtausgaben in einer Kategorie in einem bestimmten Zeitraum von Tagen zurückgibt. Die Funktion soll außerdem den Fall unterscheiden, dass die angegebene Kategorie in den Ausgabenaufzeichnungen nicht vorkommt, und den Fall, dass es im angegebenen Zeitraum keine Ausgaben dieser Kategorie gibt. Wenn die Kategorie in keiner der Aufzeichnungen vorkommt (unabhängig vom Zeitraum), soll die Funktion einen Fehler zurückgeben.

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
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Go Exercism

Bereit, mit Ausgaben zu starten?

Melde dich bei Exercism an, um Go mit 34 Konzepte165 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.