diff --git a/go/00-hello-world/go.mod b/go/00-hello-world/go.mod index d565f37..1237dae 100644 --- a/go/00-hello-world/go.mod +++ b/go/00-hello-world/go.mod @@ -1,3 +1,3 @@ -module gitty.informatik.th-mannheim.de/steger/pr3-sose2026/go/00-hello-world/hello-world +module gitty.informatik.th-mannheim.de/steger/pr3-ws202526/go/00-hello-world/hello-world go 1.26.0 diff --git a/go/01-basics/calculator.go b/go/01-basics/calculator.go index 5240c30..19af19d 100644 --- a/go/01-basics/calculator.go +++ b/go/01-basics/calculator.go @@ -13,33 +13,23 @@ func main() { exit = "exit" ) - //TODO: implement according to README.md fmt.Println("Welcome to the Simple Calculator!") for { var operation string fmt.Print("\nEnter operation (add, subtract, multiply, divide, exit): ") fmt.Scan(&operation) - //the following code just demonstrates how to use fmt.Scan if operation == exit { fmt.Println("Goodbye!") break } - var str string - fmt.Print("Please enter a string: ") - fmt.Scan(&str) var num1, num2 float64 fmt.Print("Enter the first number: ") fmt.Scan(&num1) fmt.Print("Enter the second number: ") fmt.Scan(&num2) - var x float64 - fmt.Print("Please enter a float: ") - fmt.Scan(&x) - - fmt.Printf("You entered '%s' and %f\n", str, x) switch operation { case add: fmt.Printf("Result: %.2f\n", num1+num2) diff --git a/go/01-basics/inventory.go b/go/01-basics/inventory.go index 61643ba..68d4daa 100644 --- a/go/01-basics/inventory.go +++ b/go/01-basics/inventory.go @@ -1,15 +1,20 @@ - package main +package main -import "fmt" - -type Category int - -const ( - Electronics Category = iota - Groceries - Clothes +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: @@ -23,99 +28,93 @@ func (c Category) String() string { } } -// 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, 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) +// implementation of the fmt.Stringer interface for printing products +func (p Product) String() string { + return fmt.Sprintf("%s (%s, Price: $%.2f, Quantity: %d)", p.Name, p.Category, p.Price, p.Quantity) } -// Remove a product from the inventory -func removeProduct(inventory *[]Product, name string) { - //TODO: implement - for i, product := range *inventory { - if product.Name == name { - *inventory = append((*inventory)[:i], (*inventory)[i+1:]...) - fmt.Printf("Removed product: %s\n", name) - return - } +func addProduct(inventory map[string]Product, name string, price float64, quantity int, category Category) { + + //do not create a new product in case a product with the name already exists + if _, exists := inventory[name]; exists { + fmt.Printf("Unable to add new Product %s: Product already exists.\n", name) + return } - fmt.Println("Product not found.") + + //create a new product... + p := Product{name, price, quantity, category} + inventory[name] = p // ...and insert it into the map } -// Update the quantity of a product -func updateQuantity(inventory *[]Product, name string, newQuantity int) { - //TODO: implement - 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.") +func removeProduct(inventory map[string]Product, name string) { + //no checks required. Does nothing if the product doesn't exist. + delete(inventory, name) } -// Display the inventory -func displayInventory(inventory []Product) { - //TODO: implement - fmt.Println("Inventory:") - for _, product := range inventory { - fmt.Printf("%s - %s (Price: $%.2f, Quantity: %d)\n", - product.Name, product.Category, product.Price, product.Quantity) +func updateQuantity(inventory map[string]Product, name string, newQuantity int) { + p := inventory[name] //read from the map + p.Quantity = newQuantity //update value + inventory[name] = p //store back into the map +} + +func displayInventory(inventory map[string]Product) { + //Iterate over the inventory + i := 1 + for _, p := range inventory { //don't care about the key (_), only the value (p) is relevant + fmt.Printf("%d. %s\n", i, p) + i++ } } 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}, + + fmt.Println("Welcome to the Inventory Manager!") + + // Store a fixed list of initial product names in an array. + initialProductNames := [...]string{"Laptop"} + + // Convert the array into a slice so the inventory can grow or shrink dynamically. + productNames := initialProductNames[:] + productNames = append(productNames, "Apples", "T-shirt") + + // Use a map to associate product names with details like quantity, price, and category. + prices := []float64{1000.0, 2.0, 10.0} + quantities := []int{5, 50, 20} + categories := []Category{Electronics, Groceries, Clothes} + + inventory := make(map[string]Product) + for i, name := range productNames { + inventory[name] = Product{name, prices[i], quantities[i], categories[i]} } // Display initial inventory + fmt.Println("\nInitial Inventory:") displayInventory(inventory) // Add a new product - addProduct(&inventory, "Phone", 800, 10, "Electronics") - addProduct(&inventory, "Phone", 800, 10, Electronics) + fmt.Println("\nAdding a new product: Phone (Electronics, Price: $800, Quantity: 10)") + addProduct(inventory, "Phone", 800, 10, Electronics) // Display updated inventory + fmt.Println("\nUpdated Inventory:") displayInventory(inventory) // Update the quantity of an existing product - updateQuantity(&inventory, "Apples", 30) + fmt.Println("\nUpdating quantity for Apples: New Quantity = 30") + updateQuantity(inventory, "Apples", 30) // Remove a product - removeProduct(&inventory, "T-shirt") + fmt.Println("\nRemoving product: T-shirt") + removeProduct(inventory, "T-shirt") // Display final inventory + fmt.Println("\nFinal Inventory:") displayInventory(inventory) -} \ No newline at end of file +} 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 index f50d633..e1311ab 100644 --- a/go/02-next-level/02-struct-embedding.go +++ b/go/02-next-level/02-struct-embedding.go @@ -11,12 +11,12 @@ func (p Person) Say() { } type Student struct { - Person //struct Person is embedded + Person //struct Person is embedded Semester int } type Teacher struct { - Person //struct Person is embedded + Person //struct Person is embedded Subject string } 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) }