diff --git a/go/01-basics/inventory.go b/go/01-basics/inventory.go index 209c374..79e4868 100644 --- a/go/01-basics/inventory.go +++ b/go/01-basics/inventory.go @@ -1,40 +1,107 @@ package main +import "fmt" + +// a new type named Category that can hold integer values +type Category int + +// The go equivalent of an enum that enumerates different named Categories +const ( + Electronics Category = iota //0 + Groceries //1 + Clothes //2 +) + +// implementation of the fmt.Stringer interface. In that way, Categories can be printed using the fmt.Print* functions +func (c Category) String() string { + switch c { + case Electronics: + return "Electronics" + case Groceries: + return "Groceries" + case Clothes: + return "Clothes" + default: + return "Unknown" + } +} + +// Struct for product details type Product struct { Name string Price float64 Quantity int - Category string //TODO: use enum instead + Category Category } -func addProduct(inventory *[]Product, name string, price float64, quantity int, category string) { - //TODO: implement +// Add a new product to the inventory +func addProduct(inventory *[]Product, //pointer to the slice of of Products that shall be modified. In that way it can serve as in input/output parameter + name string, price float64, quantity int, category Category) { + // Check if the product already exists in the inventory + for _, product := range *inventory { + if product.Name == name { + fmt.Println("Product already exists.") + return + } + } + // Add the new product + *inventory = append(*inventory, Product{ + Name: name, + Price: price, + Quantity: quantity, + Category: category, + }) + fmt.Printf("Added product: %s\n", name) } -func removeProduct(inventory *[]Product, name string) { - //TODO: implement +// Remove a product from the inventory +func removeProduct(inventory *[]Product, //pointer to the slice of of Products that shall be modified. In that way it can serve as in input/output parameter + name string) { + for i, product := range *inventory { + if product.Name == name { + *inventory = append((*inventory)[:i], (*inventory)[i+1:]...) + fmt.Printf("Removed product: %s\n", name) + return + } + } + fmt.Println("Product not found.") } -func updateQuantity(inventory *[]Product, name string, newQuantity int) { - //TODO: implement +// Update the quantity of a product. +func updateQuantity(inventory *[]Product, //pointer to the slice of of Products that shall be modified. In that way it can serve as in input/output parameter + name string, newQuantity int) { + for i, product := range *inventory { + if product.Name == name { + (*inventory)[i].Quantity = newQuantity + fmt.Printf("Updated quantity for %s: New Quantity = %d\n", name, newQuantity) + return + } + } + fmt.Println("Product not found.") } +// Display the inventory func displayInventory(inventory []Product) { - //TODO: implement + fmt.Println("Inventory:") + for _, product := range inventory { //iterating over a range returns pairs of index and the content of a container (here: inventory). Since we don't care about the index, we use _ as a placeholder. + fmt.Printf("%s - %s (Price: $%.2f, Quantity: %d)\n", + product.Name, product.Category, product.Price, product.Quantity) + } } func main() { + // Initialize inventory inventory := []Product{ - {Name: "Laptop", Price: 1000, Quantity: 5, Category: "Electronics"}, - {Name: "Apples", Price: 2, Quantity: 50, Category: "Groceries"}, - {Name: "T-shirt", Price: 10, Quantity: 20, Category: "Clothes"}, + {Name: "Laptop", Price: 1000, Quantity: 5, Category: Electronics}, + {Name: "Apples", Price: 2, Quantity: 50, Category: Groceries}, + {Name: "T-shirt", Price: 10, Quantity: 20, Category: Clothes}, } // Display initial inventory displayInventory(inventory) // Add a new product - addProduct(&inventory, "Phone", 800, 10, "Electronics") + addProduct(&inventory, "Phone", 800, 10, Electronics) // Display updated inventory displayInventory(inventory) diff --git a/go/02-next-level/00-methods.go b/go/02-next-level/00-methods.go new file mode 100644 index 0000000..ae1a6ac --- /dev/null +++ b/go/02-next-level/00-methods.go @@ -0,0 +1,26 @@ +package main + +import "fmt" + +type Counter struct { + value int +} + +// pointer receiver. The method operates directly on the object c points to +// Use a pointer receiver whenever the object needs to be modified. +func (c *Counter) Increment() { + c.value++ +} + +// value receiver. The method operates on copy of object c. Use a value +// receiver when the object does not need to be modified in the method. +func (c Counter) String() string { + return fmt.Sprintf("Counter value: %d", c.value) +} + +func main() { + c := Counter{} + fmt.Println(c) + c.Increment() + fmt.Println(c) +} diff --git a/go/02-next-level/01-interfaces.go b/go/02-next-level/01-interfaces.go new file mode 100644 index 0000000..c0eb97f --- /dev/null +++ b/go/02-next-level/01-interfaces.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "math" +) + +type geometry interface { + area() float64 + perim() float64 +} + +type rect struct { + width, height float64 +} + +// Ensure that rect implements the geometry interface +var _ geometry = rect{} + +type circle struct { + radius float64 +} + +// Ensure that circle implements the geometry interface +var _ geometry = circle{} + +func (r rect) area() float64 { + return r.width * r.height +} +func (r rect) perim() float64 { + return 2*r.width + 2*r.height +} + +func (c circle) area() float64 { + return math.Pi * c.radius * c.radius +} +func (c circle) perim() float64 { + return 2 * math.Pi * c.radius +} + +//Metods demonstrates how to use an interface +func measure(g geometry) { + fmt.Println(g) + fmt.Println(g.area()) + fmt.Println(g.perim()) +} + + +//Method demonstrates casting an interface to a concrete type. +//Should be avoided in practice whenever possible. +func detectCircle(g geometry) { + if c, ok := g.(circle); ok { + fmt.Println("circle with radius", c.radius) + } +} + +func main() { + r := rect{width: 3, height: 4} + c := circle{radius: 5} + + measure(r) + measure(c) + + detectCircle(r) + detectCircle(c) +} diff --git a/go/02-next-level/02-struct-embedding.go b/go/02-next-level/02-struct-embedding.go new file mode 100644 index 0000000..e1311ab --- /dev/null +++ b/go/02-next-level/02-struct-embedding.go @@ -0,0 +1,40 @@ +package main + +import "fmt" + +type Person struct { + Name string +} + +func (p Person) Say() { + fmt.Println("Hi, my name is", p.Name) +} + +type Student struct { + Person //struct Person is embedded + Semester int +} + +type Teacher struct { + Person //struct Person is embedded + Subject string +} + +func main() { + + max := Person{"Max"} + + daniel := Student{Person{"Daniel"}, 3} + + sebastian := Teacher{Person{"Sebastian"}, "PR3"} + + max.Say() + + daniel.Say() //can be invoked directly on Student + + sebastian.Say() //can be invoked directly on Teacher + + max = sebastian.Person + + max.Say() +} diff --git a/go/02-next-level/03-errors.go b/go/02-next-level/03-errors.go new file mode 100644 index 0000000..6e68e3b --- /dev/null +++ b/go/02-next-level/03-errors.go @@ -0,0 +1,62 @@ +package main + +import ( + "errors" + "fmt" +) + +// a function that can fail, so it returns an error as the second return value +func f(arg int) (int, error) { + if arg == 42 { + // in case of an error, return the zero value for the result and a non-nil error + return 0, errors.New("can't work with 42") + } + + // simply return the result and a nil error in the success case + return arg + 3, nil +} + +// define some errors to use in makeTea +var ErrOutOfTea = fmt.Errorf("no more tea available") +var ErrPower = fmt.Errorf("can't boil water") + +// a function that can fail in multiple ways, so it returns an error as the second return value +func makeTea(arg int) error { + if arg == 2 { + // return the error directly, so we can check for it later with errors.Is + return ErrOutOfTea + } else if arg == 4 { + // use %w to wrap the error, so we can check for it later with errors.Is + return fmt.Errorf("making tea: %w", ErrPower) + } + return nil +} + +func main() { + for _, i := range []int{7, 42} { + + //typical pattern for checking errors in Go: if the error is not nil, handle it and return or continue; otherwise, use the result + if r, e := f(i); e != nil { + fmt.Println("f failed:", e) + } else { + fmt.Println("f worked:", r) + } + } + + for i := range 5 { + if err := makeTea(i); err != nil { + + // use errors.Is to check for specific errors, even if they are wrapped + if errors.Is(err, ErrOutOfTea) { + fmt.Println("We should buy new tea!") + } else if errors.Is(err, ErrPower) { // this error is wrapped, but errors.Is can still check for it + fmt.Println("Now it is dark.") + } else { + fmt.Printf("unknown error: %s\n", err) + } + continue + } + + fmt.Println("Tea is ready!") + } +} diff --git a/go/02-next-level/04-closure.go b/go/02-next-level/04-closure.go new file mode 100644 index 0000000..a7d0781 --- /dev/null +++ b/go/02-next-level/04-closure.go @@ -0,0 +1,28 @@ +package main + +import "fmt" + +// a closure is a function value that references variables from outside its body. +// The function may access and assign to the referenced variables; in this sense the function is "bound" to the variables. +func intSeq() func() int { //returns a function that returns an int + i := 0 // i is a variable that intSeq's function value will reference. It continues to exist even after intSeq returns. + return func() int { // the anonomous function is returned here. It is not executed yet. + i++ + return i + } +} + +func main() { + + //functions are first class citizens in Go, so we can assign them to variables, + // pass them as arguments to other functions, and return them from functions. + nextInt := intSeq() + + fmt.Println(nextInt()) + fmt.Println(nextInt()) + fmt.Println(nextInt()) + + //a second function value from intSeq, with its own i variable. + newInts := intSeq() + fmt.Println(newInts()) +} diff --git a/go/02-next-level/05-generics.go b/go/02-next-level/05-generics.go index 5481cb1..ac9e9fe 100644 --- a/go/02-next-level/05-generics.go +++ b/go/02-next-level/05-generics.go @@ -2,7 +2,10 @@ package main import "fmt" -func SlicesIndex[S []E, E int](s []string, v string) int { +// a generic function that takes a slice of any type and a value of that type, and returns the index of the value in the slice, or -1 if it is not found. +// The type parameters S and E are declared in square brackets before the function name, and they can be used in the function signature and body. +// The constraint comparable means that the type E must support the == operator, which is necessary for comparing the value with the elements of the slice. +func SlicesIndex[S []E, E comparable](s S, v E) int { for i := range s { if v == s[i] { return i @@ -11,27 +14,34 @@ func SlicesIndex[S []E, E int](s []string, v string) int { return -1 } -type List struct { - head, tail *element +// represents a linked list of any type T. The type parameter T is used in the definition of List and its methods, +// and it can be instantiated with any type when we create a List value. The type any is in fact defined as +// an empty interface. Thanks to duck-typing it can be used to represent any type, but it does not provide any +// operations on the values of that type. +type List[T any] struct { + head, tail *element[T] } -type element struct { - next *element - val int +// the generic element of the linked list +type element[T any] struct { + next *element[T] + val T } -func (lst *List) Push(v int) { +// adds an instance of the generic type to the list +func (lst *List[T]) Push(v T) { if lst.tail == nil { - lst.head = &element{val: v} + lst.head = &element[T]{val: v} lst.tail = lst.head } else { - lst.tail.next = &element{val: v} + lst.tail.next = &element[T]{val: v} lst.tail = lst.tail.next } } -func (lst *List) AllElements() []int { - var elems []int +// returns all elements of the list as a slice of the generic type. +func (lst List[T]) AllElements() []T { + var elems []T for e := lst.head; e != nil; e = e.next { elems = append(elems, e.val) } @@ -39,13 +49,21 @@ func (lst *List) AllElements() []int { } func main() { + var s = []string{"foo", "bar", "zoo"} + // The types S and E are explicitly specified as []string and string respectively. + fmt.Println("index of zoo:", SlicesIndex[[]string, string](s, "zoo")) - fmt.Println("index of zoo:", SlicesIndex(s, "zoo")) + var s2 = []int{2, 4, 5, 6} + // The type E is inferred from the type of the value 4, which is int, + // and the type S is inferred from the type of the slice s2, which is []int. + fmt.Println("index of 4: ", SlicesIndex(s2, 4)) - lst := List{} + // instantiate a list of float64 elements + lst := List[float64]{} lst.Push(10) lst.Push(13) lst.Push(23) + lst.Push(23.3) fmt.Println("list:", lst.AllElements()) } diff --git a/go/02-next-level/06-defer.go b/go/02-next-level/06-defer.go index 998103c..e0af38a 100644 --- a/go/02-next-level/06-defer.go +++ b/go/02-next-level/06-defer.go @@ -6,18 +6,22 @@ import ( ) func main() { - f := createFile("/tmp/defer.txt") - writeFile(f) - closeFile(f) -} -func createFile(p string) *os.File { fmt.Println("creating") - f, err := os.Create(p) + f, err := os.Create("/tmp/defer.txt") + + // defer the closing of the file until the surrounding function returns. + // This ensures that the file will be closed even if there is a panic or an early return in the function. + defer closeFile(f) + if err != nil { panic(err) } - return f + + //will not be called in case of a panic before + writeFile(f) + + // closeFile will be called here due to the defer-statement, even in case of a panic before. } func writeFile(f *os.File) { @@ -27,8 +31,10 @@ func writeFile(f *os.File) { func closeFile(f *os.File) { fmt.Println("closing") - err := f.Close() - + var err error + if f != nil { + err = f.Close() + } if err != nil { panic(err) } diff --git a/go/03-modules/go.mod b/go/03-modules/go.mod new file mode 100644 index 0000000..d5f08c0 --- /dev/null +++ b/go/03-modules/go.mod @@ -0,0 +1,5 @@ +module gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/03-modules + +go 1.26.1 + +require github.com/google/uuid v1.6.0 diff --git a/go/03-modules/go.sum b/go/03-modules/go.sum new file mode 100644 index 0000000..7790d7c --- /dev/null +++ b/go/03-modules/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/go/03-modules/main.go b/go/03-modules/main.go index 7905807..d32a521 100644 --- a/go/03-modules/main.go +++ b/go/03-modules/main.go @@ -1,5 +1,27 @@ package main -func main() { +import ( + "fmt" + // importing the myMath package with an alias mm. This allows us to use mm instead of myMath to access the functions in the package. + // go packages are imported using their full path, which is the path to the package relative to the GOPATH or module root. . + mm "gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/03-modules/myMath" + + // the underscore is used to import a package solely for its side effects (i.e., the init function). + // This is useful when you want to initialize a package but do not need to access any of its exported functions or variables. + // _ "gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/03-modules/myMath" + + // importing a third-party package from GitHub. This package provides functions for generating UUIDs (Universally Unique Identifiers). + // To use this package, you need to run go get github.com/google/uuid to download and install the package in your Go workspace. + // Alternatively, you can run go mod tidy to automatically download and install any missing dependencies based on the imports in your code. + "github.com/google/uuid" + //run go get github.com/google/uuid + //or go mod tidy => go.sum +) + +func main() { + fmt.Println(mm.Add(1, 2)) + + id := uuid.New() + fmt.Println("Generated UUID:", id) } diff --git a/go/03-modules/myMath/math.go b/go/03-modules/myMath/math.go index e69de29..f348cd8 100644 --- a/go/03-modules/myMath/math.go +++ b/go/03-modules/myMath/math.go @@ -0,0 +1,27 @@ +package myMath + +import "fmt" + +// init is a special function that is called automatically when the package is imported. It is used to initialize the package +// and can be used to set up any necessary state or perform any necessary setup before the package is used. +func init() { + fmt.Println("initializing myMath") +} + +// functions that start with an uppercase letter are exported and can be accessed from outside the package +func Add(a, b int) int { + return a + b +} + +// functions that start with a lowercase letter are not exported and cannot be accessed from outside the package +func greater(a, b int) bool { + return a > b +} + +func Min(a, b int) int { + if greater(a, b) { + return b + } else { + return a + } +} diff --git a/go/03-modules/myMath/math_test.go b/go/03-modules/myMath/math_test.go new file mode 100644 index 0000000..303d05e --- /dev/null +++ b/go/03-modules/myMath/math_test.go @@ -0,0 +1,35 @@ +package myMath_test + +import ( + "testing" + + "gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/03-modules/myMath" +) + +func TestAdd(t *testing.T) { + sum := myMath.Add(2, 3) + if sum != 5 { + t.Errorf("Add(2,3) = %v, want %v", sum, 5) + } +} + +func TestMin(t *testing.T) { + type args struct { + in0 int + in1 int + } + tests := []struct { + name string + args args + want int + }{ + {"first greater", args{3, 2}, 2}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := myMath.Min(tt.args.in0, tt.args.in1); got != tt.want { + t.Errorf("Min() = %v, want %v", got, tt.want) + } + }) + } +}