1
0
Fork 0

Compare commits

..

13 Commits

Author SHA1 Message Date
ChaosLL1337 78cbe47176 Unterricht stunde 28.10 2026-10-28 10:07:02 +00:00
ChaosLL1337 bf881b9671 Beginn aufgabe Design Pattern 2026-10-22 09:37:18 +00:00
Leon Maximilian Löhle 623b10273f Merge pull request 'main' (#5) from steger/pr3-ws202627:main into main
Reviewed-on: https://gitty.informatik.th-mannheim.de/3010261/pr3-ws202627/pulls/5
2026-10-22 10:49:55 +02:00
ChaosLL1337 e17734dab4 Merge remote-tracking branch 'upstream/main'
# Conflicts:
#	go/01-basics/calculator.go
#	go/01-basics/inventory.go
#	go/02-next-level/02-struct-embedding.go
2026-10-21 06:52:31 +00:00
ChaosLL1337 96daf2f7e2 Besser 2026-10-21 06:49:20 +00:00
ChaosLL1337 7d84b5be29 fgh 2026-10-21 06:47:19 +00:00
ChaosLL1337 81a12a4f06 Lösungen geholt 2026-10-21 06:44:08 +00:00
ChaosLL1337 eaa0a8b7de Ja das ist doch wunderbar 2026-10-21 06:19:47 +00:00
Leon Maximilian Löhle e3a517a931 Merge pull request 'main' (#1) from steger/pr3-ws202627:main into main
Reviewed-on: https://gitty.informatik.th-mannheim.de/3010261/pr3-ws202627/pulls/1
2026-10-14 09:38:39 +02:00
ChaosLL1337 efb04df47d .- 2026-10-14 07:34:01 +00:00
ChaosLL1337 e220ea6031 Calcu und Inventory 2026-10-14 08:44:49 +02:00
ChaosLL1337 f335aace32 Aufgaben bearbeitet 2026-10-07 08:27:43 +02:00
ChaosLL1337 34a8506c81 sd 2026-09-23 09:13:30 +00:00
61 changed files with 566 additions and 2330 deletions

View File

@ -0,0 +1,9 @@
{
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "2.17.0",
"resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c",
"integrity": "sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c"
}
}
}

View File

@ -0,0 +1,26 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-in-docker
{
"name": "Docker in Docker",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:bookworm",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "latest",
"moby": "true"
}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "docker --version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

12
.github/dependabot.yml vendored 100644
View File

@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly

View File

@ -1,7 +1,7 @@
FROM golang:1.27-bookworm FROM golang:1.27-bookworm
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y build-essential \ && apt-get install -y build-essential ripgrep \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN apt-get update \ RUN apt-get update \

View File

@ -5,11 +5,16 @@
}, },
"postCreateCommand": "go install github.com/go-delve/delve/cmd/dlv@latest && go install github.com/cweill/gotests/gotests@v1.9.0 && go install golang.org/x/tools/gopls@latest && go install golang.org/x/tools/cmd/goimports@latest && go install honnef.co/go/tools/cmd/staticcheck@latest", "postCreateCommand": "go install github.com/go-delve/delve/cmd/dlv@latest && go install github.com/cweill/gotests/gotests@v1.9.0 && go install golang.org/x/tools/gopls@latest && go install golang.org/x/tools/cmd/goimports@latest && go install honnef.co/go/tools/cmd/staticcheck@latest",
"customizations": { "customizations": {
"vscode": { "extensions": [ "vscode": {
"golang.go", "extensions": [
"gruntfuggly.todo-tree", "golang.go",
"-github.copilot" "gruntfuggly.todo-tree",
]} "-github.copilot"
],
"settings": {
"todo-tree.ripgrep.ripgrep": "/usr/bin/rg"
}
}
}, },
"remoteUser": "developer", "remoteUser": "developer",
"updateRemoteUserUID": true "updateRemoteUserUID": true

View File

@ -2,6 +2,11 @@ package main
import "fmt" import "fmt"
func main() { //go:fix inline
fmt.Println("hello world") func varOf(x int) *int {
return new(x)
}
func main() {
fmt.Println(new(10))
} }

View File

@ -14,6 +14,11 @@ func plus(a int, b int) int {
// return a + b // return a + b
// } // }
func plus2(a int, b int) (result int) {
result = a + b
return
}
// A function with multiple parameters of the same type can be shortened by listing the type only once. // A function with multiple parameters of the same type can be shortened by listing the type only once.
func plusPlus(a, b, c int) int { func plusPlus(a, b, c int) int {
return a + b + c return a + b + c
@ -26,6 +31,10 @@ func plusNamed(a, b int) (result int) {
return return
} }
func nonplusultra(a int, b int) (result int, description string) {
return a + b, fmt.Sprintf("the sum of %d and %d is %d, a, b, a+b")
}
// A function can return multiple values. Here we return the sum and a formatted string description of the operation. // A function can return multiple values. Here we return the sum and a formatted string description of the operation.
func plusDescription(a int, b int) (int, string) { func plusDescription(a int, b int) (int, string) {
result := a + b result := a + b
@ -36,6 +45,7 @@ func main() {
res := plus(1, 2) res := plus(1, 2)
fmt.Println("1+2 =", res) fmt.Println("1+2 =", res)
fmt.Println(plus(30, 18))
res = plusPlus(1, 2, 3) res = plusPlus(1, 2, 3)
fmt.Println("1+2+3 =", res) fmt.Println("1+2+3 =", res)

View File

@ -0,0 +1,118 @@
package main
import (
"fmt"
"math"
)
const h string = "constant"
// zeroval bekommt eine KOPIE: Änderung wirkt nur lokal.
func zeroval(ival int) {
ival = 0
}
// zeroptr bekommt die ADRESSE: Änderung wirkt auf das Original.
func zeroptr(iptr *int) {
*iptr = 0
}
func basics() {
fmt.Println("go", "lang")
fmt.Println("go" + "lang")
fmt.Println(7 / 3) // 2 (Ganzzahldivision)
fmt.Println(true || false)
fmt.Println(!true)
}
func variables() {
var a = "Initial" // Typ wird inferiert
fmt.Println(a)
a = "string"
fmt.Println(a)
var b int // Nullwert: 0
var c = 4
var x, y, z int = 1, 3, 6
fmt.Println(b, c)
fmt.Println(x, y, z)
s, z := "string", 8 // s neu, z nur neu zugewiesen
fmt.Println(s, z)
svar := h
fmt.Println(svar)
}
func constants() {
const n = 500000
const q = 3e20 / n // untypisiert, wird zur Compile-Zeit berechnet
fmt.Println(q) // 6e+14
i := int64(n)
fmt.Println(math.Sin(float64(i)))
fmt.Println(math.Sin(n)) // untypisierte Konstante passt sich an
}
func controlFlow() {
// Schleife mit Abbruchbedingung
count := 0
for {
fmt.Println("hello")
count++
if count >= 3 {
break
}
}
j := 2
switch j {
case 1:
fmt.Println("one")
case 2, 3:
fmt.Println("two or three")
default:
fmt.Println("other")
}
}
func arrays() {
var u [5]int
u[2] = 4
v := [...]int{1, 3, 4, 5, 6, 3} // Länge wird vom Compiler gezählt
fmt.Println(u, v, len(v))
}
func pointers() {
t := 1
zeroval(t)
fmt.Println(t) // 1, unverändert
zeroptr(&t)
fmt.Println(t) // 0, verändert
j := 2
p := &j
fmt.Println(*p) // 2 (Dereferenzierung)
}
func slices() {
a := []int{}
a = append(a, 5, 6, 6, 9, 7, 8)
a = append(a, 6)
a = append(a, 7)
a[3] = 99
a = append(a, 8)
b := a
a = append(a, 9)
fmt.Println(a, len(a))
fmt.Println(b)
}
func main() {
basics()
variables()
constants()
controlFlow()
arrays()
pointers()
slices()
}

View File

@ -0,0 +1,74 @@
package main
import (
"fmt"
)
const (
opAdd = "add"
opSubtract = "subtract"
opMultiply = "multiply"
opDivide = "divide"
opExit = "exit"
)
var result float64
func add(a, b float64) {
result = a + b
}
func multiply(a, b float64) {
result = a * b
}
func subtract(a, b float64) {
result = a - b
}
func divide(a, b float64) {
result = a / b
}
func main() {
var x float64
var y float64
//TODO: implement according to README.md
//the following code just demonstrates how to use fmt.Scan
for {
fmt.Print("What would you like to do? (add|subtract|divide|multiply|exit)")
var str string
fmt.Scan(&str)
if str == opExit {
break
} else {
fmt.Print("Please enter a float: ")
fmt.Scan(&x)
fmt.Print("Please enter a float: ")
fmt.Scan(&y)
switch str {
case opAdd:
add(x, y)
fmt.Println(result)
case opMultiply:
multiply(x, y)
fmt.Println(result)
case opDivide:
if y == 0 {
fmt.Println("Division by 0 is not possible")
continue
}
divide(x, y)
fmt.Println(result)
case opSubtract:
subtract(x, y)
fmt.Println(result)
default:
fmt.Println("Unknown operation")
}
}
}
}

View File

@ -0,0 +1,3 @@
module gitty.informatik.th-mannheim.de/steger/pr3-sose2026/go
go 1.26.0

View File

@ -0,0 +1,41 @@
package main
import (
"fmt"
"maps"
)
func main() {
m := make(map[string]int)
m["k1"] = 7
m["k2"] = 13
fmt.Println("map:", m)
v1 := m["k1"]
fmt.Println("v1:", v1)
v3 := m["k3"]
fmt.Println("v3:", v3)
fmt.Println("len:", len(m))
delete(m, "k2")
fmt.Println("map:", m)
clear(m)
fmt.Println("map:", m)
_, prs := m["k2"]
fmt.Println("prs:", prs)
n := map[string]int{"foo": 1, "bar": 2}
fmt.Println("map:", n)
n2 := map[string]int{"foo": 1, "bar": 2}
if maps.Equal(n, n2) {
fmt.Println("n == n2")
}
}

View File

@ -0,0 +1,58 @@
package main
import (
"fmt"
"slices"
)
func main() {
var s []string
fmt.Println("uninit:", s, s == nil, len(s) == 0)
s = make([]string, 3)
fmt.Println("emp:", s, "len:", len(s), "cap:", cap(s))
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("set:", s)
fmt.Println("get:", s[2])
fmt.Println("len:", len(s))
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println("apd:", s)
c := make([]string, len(s))
copy(c, s)
fmt.Println("cpy:", c)
l := s[2:5]
fmt.Println("sl1:", l)
l = s[:5]
fmt.Println("sl2:", l)
l = s[2:]
fmt.Println("sl3:", l)
t := []string{"g", "h", "i"}
fmt.Println("dcl:", t)
t2 := []string{"g", "h", "i"}
if slices.Equal(t, t2) {
fmt.Println("t == t2")
}
twoD := make([][]int, 3)
for i := range 3 {
innerLen := i + 1
twoD[i] = make([]int, innerLen)
for j := range innerLen {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}

View File

@ -0,0 +1,23 @@
package main
import "fmt"
type Counter struct {
value int
}
func (c Counter /** value reciever **/) toString() string {
return fmt.Sprintf("Counter Value: %d", c.value)
}
func (c *Counter /**Pointer reciever**/) Increment() {
fmt.Printf("Inside Increment: %p\n", c)
c.value = c.value + 1
}
func main() {
c := Counter{}
fmt.Println(c)
c.Increment()
fmt.Println(c)
}

View File

@ -1,4 +1,4 @@
module gitty.informatik.th-mannheim.de/steger/pr3-ws202627/go/03-modules module gitty.informatik.th-mannheim.de/3010261/pr3-ws202627/go/03-modules
go 1.25.0 go 1.25.0

View File

@ -3,7 +3,7 @@ package myMath_test
import ( import (
"testing" "testing"
"gitty.informatik.hs-mannheim.de/steger/pr3-code/go/03-modules/myMath" "gitty.informatik.hs-mannheim.de/3010261/pr3-code/go/03-modules/myMath"
) )
func TestAdd(t *testing.T) { func TestAdd(t *testing.T) {

View File

@ -1,3 +1,3 @@
module gitty.informatik.hs-mannheim.de/steger/pr3-code/go/04-design-pattern module gitty.informatik.th-mannheim.de/3010261/pr3-ws202627/go/04-design-pattern
go 1.25.0 go 1.27.3

View File

@ -2,80 +2,28 @@ package main
import ( import (
"fmt" "fmt"
"os"
"gitty.informatik.hs-mannheim.de/steger/pr3-code/go/04-design-pattern/patterns" "gitty.informatik.th-mannheim.de/3010261/pr3-ws202627/go/04-design-pattern/patterns"
) )
type ConstValueCalculator float64
func NewConstValueCalculator(valueString string) *ConstValueCalculator {
var value float64
if _, err := fmt.Sscanf(valueString, "%f", &value); err != nil {
return nil
}
cvc := ConstValueCalculator(value)
return &cvc
}
func (c ConstValueCalculator) Calc() float64 {
return float64(c)
}
/*
parses the expression and builds a binary calculation tree. Returns the tree, the remaining expression after parsing, and an error
example Input:
1. [2] => 2
2. [+ 2 3] => 5
3. [* 2 3] => 6
4. [+ * 2 3 4] == [+ 6 4] => 10
*/
func buildBinaryCalculationTree(expression []string) (patterns.Calculator, []string, error) {
if len(expression) == 0 {
return nil, nil, fmt.Errorf("empty argument")
}
//1. attempt: try a composite calculator from an operator
composite := patterns.NewCompositeCalculatorFromOperator(expression[0])
if composite != nil {
expression = expression[1:]
for i := 0; i < 2; i++ {
arg, remaining, err := buildBinaryCalculationTree(expression)
if err != nil {
return nil, nil, err
}
expression = remaining
composite.Add(arg)
}
return composite, expression, nil
}
//2. attempt: try a const value calculator from a value
constValue := NewConstValueCalculator(expression[0])
if constValue != nil {
return constValue, expression[1:], nil
}
//3. failure, no valid symbol
return nil, nil, fmt.Errorf("invalid symbol: %s", expression[0])
}
// run for example with go run . + "*" 2 2 4
// note the "*" because * would otherwise expand
func main() { func main() {
calc, remainingArgs, err := buildBinaryCalculationTree(os.Args[1:]) noodle, err := patterns.NewNoodleMaker().
Name("Spaghetti").
Type("Lang").
Length(25).
Girth(2).
Build()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println("Fehler:", err)
return return
} }
fmt.Printf("%+v\n", noodle)
if len(remainingArgs) != 0 { standard, _ := patterns.NewNoodleMaker().Build()
fmt.Println("Invalid Input - remaining arguments", remainingArgs) fmt.Printf("%+v\n", standard)
return
_, err = patterns.NewNoodleMaker().Length(0).Build()
if err != nil {
fmt.Println("Erwarteter Fehler:", err)
} }
fmt.Println(calc.Calc())
} }

View File

@ -1,41 +0,0 @@
package patterns
type Calculator interface {
Calc() float64
}
type Aggregator func(float64, float64) float64
type CompositeCalculator struct {
initialValue float64
aggregator Aggregator
children []Calculator
}
var _ Calculator = CompositeCalculator{}
func NewCompositeCalculator(initialValue float64, aggregator Aggregator) *CompositeCalculator {
return &CompositeCalculator{initialValue, aggregator, nil}
}
func NewCompositeCalculatorFromOperator(operator string) *CompositeCalculator {
switch operator {
case "+":
return NewCompositeCalculator(0.0, func(a, b float64) float64 { return a + b })
case "*":
return NewCompositeCalculator(1.0, func(a, b float64) float64 { return a * b })
}
return nil
}
func (c CompositeCalculator) Calc() float64 {
result := c.initialValue
for _, child := range c.children {
result = c.aggregator(result, child.Calc())
}
return result
}
func (c *CompositeCalculator) Add(child Calculator) {
c.children = append(c.children, child)
}

View File

@ -1,71 +0,0 @@
package patterns_test
import (
"testing"
"gitty.informatik.hs-mannheim.de/steger/pr3-code/go/04-design-pattern/patterns"
)
type valueCalc float64
func (v valueCalc) Calc() float64 {
return float64(v)
}
func TestCompositeCalculator_Empty(t *testing.T) {
empty := patterns.NewCompositeCalculator(5.0, func(a, b float64) float64 { return a + b })
got := empty.Calc()
want := 5.0
if got != want {
t.Errorf("Empty: got %v, want %v", got, want)
}
}
func TestCompositeCalculator_Addition(t *testing.T) {
add := patterns.NewCompositeCalculatorFromOperator("+")
add.Add(valueCalc(5))
add.Add(valueCalc(3))
add.Add(valueCalc(2))
got := add.Calc()
want := 10.0
if got != want {
t.Errorf("Addition: got %v, want %v", got, want)
}
}
func TestCompositeCalculator_Multiplication(t *testing.T) {
mul := patterns.NewCompositeCalculatorFromOperator("*")
mul.Add(valueCalc(2))
mul.Add(valueCalc(3))
mul.Add(valueCalc(4))
got := mul.Calc()
want := 24.0
if got != want {
t.Errorf("Multiplication: got %v, want %v", got, want)
}
}
func TestCompositeCalculator_Nested(t *testing.T) {
// + 5 3 2 * 2 3 + 4 4 = 5 + 3 + 2 + (2 * 3 * (4+4)) = 5+3+2+48 = 58
add := patterns.NewCompositeCalculatorFromOperator("+")
add.Add(valueCalc(5))
add.Add(valueCalc(3))
add.Add(valueCalc(2))
mul := patterns.NewCompositeCalculatorFromOperator("*")
mul.Add(valueCalc(2))
mul.Add(valueCalc(3))
add2 := patterns.NewCompositeCalculatorFromOperator("+")
add2.Add(valueCalc(4))
add2.Add(valueCalc(4))
mul.Add(add2)
add.Add(mul)
got := add.Calc()
want := 58.0
if got != want {
t.Errorf("Nested: got %v, want %v", got, want)
}
}

View File

@ -0,0 +1,48 @@
package patterns
import (
"errors"
)
type Noodle struct {
Type string
Name string
Length int
Girth int
}
type NoodleMaker struct {
noodle Noodle
}
func NewNoodleMaker() *NoodleMaker {
return &NoodleMaker{
noodle: Noodle{"Röhre", "Fusilli", 10, 5},
}
}
func (b *NoodleMaker) Length(l int) *NoodleMaker {
b.noodle.Length = l
return b
}
func (c *NoodleMaker) Girth(g int) *NoodleMaker {
c.noodle.Girth = g
return c
}
func (d *NoodleMaker) Name(n string) *NoodleMaker {
d.noodle.Name = n
return d
}
func (e *NoodleMaker) Type(t string) *NoodleMaker {
e.noodle.Type = t
return e
}
func (m *NoodleMaker) Build() (Noodle, error) {
if m.noodle.Length <= 0 {
return Noodle{}, errors.New("Länge muss größer als 0 sein")
}
if m.noodle.Girth <= 0 {
return Noodle{}, errors.New("Dicke muss größer als 0 sein")
}
return m.noodle, nil
}

View File

@ -0,0 +1,48 @@
package patterns
import (
"testing"
)
func TestNoodleMaker(t *testing.T) {
tests := []struct {
name string
build func() (Noodle, error)
want Noodle
wantErr bool
}{
{
name: "Defaults",
build: func() (Noodle, error) { return newNoodleMaker().Build() },
want: Noodle{Type: "Röhre", Name: "Fusilli", Length: 10, Girth: 5},
},
{
name: "New Noodle",
build: func() (Noodle, error) {
return newNoodleMaker().Name("Spaghetti").Type("Lang").Length(25).Girth(2).Build()
},
want: Noodle{Type: "Lang", Name: "Spaghetti", Length: 25, Girth: 2},
},
{
name: "Length 0",
build: func() (Noodle, error) { return newNoodleMaker().Length(0).Build() },
wantErr: true,
},
{
name: "Girth -1",
build: func() (Noodle, error) { return newNoodleMaker().Girth(-1).Build() },
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.build()
if (err != nil) != tt.wantErr {
t.Fatalf("error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr && got != tt.want {
t.Errorf("got %+v, want %+v", got, tt.want)
}
})
}
}

View File

@ -2,64 +2,26 @@ package main
import ( import (
"fmt" "fmt"
"sync"
"time" "time"
) )
/** func print(ci <-chan int) {
* This function reads integers from a receive-only channel and prints them to the console. //TODO: implement
* It simulates some work by sleeping for 100 milliseconds after printing each integer. for value := range ci {
* The function uses a done channel to signal when it has finished processing all integers from the input channel.
* When the input channel is closed and all integers have been printed, the function sends a value to the done channel to indicate that it is done.
*
* @param ci A receive-only channel of integers that the function will read from and print. The function will continue to read from the channel until it is closed.
* @param done A send-only channel of booleans that the function will use to signal when it has finished processing the input channel. The function will send a value to this channel when it is done.
*/
func printChannel(ci <-chan int, done chan<- bool) {
defer func() { done <- true }()
for i := range ci {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
fmt.Println(i) fmt.Println(value)
}
}
/**
* This function is similar to printChannel but uses a WaitGroup instead of a done channel to signal completion.
* The WaitGroup allows us to wait for the goroutine to finish without needing a separate channel for signaling.
*
* @param ci A receive-only channel of integers that the function will read from and print. The function will continue to read from the channel until it is closed.
* @param wg A pointer to a sync.WaitGroup that will be used to signal when the goroutine has finished processing the channel.
* The WaitGroup should have its counter incremented before calling this function, and this function will call Done() on the WaitGroup when it finishes.
*/
func printWaitgroup(ci <-chan int, wg *sync.WaitGroup) {
defer func() { wg.Done() }()
for i := range ci {
time.Sleep(100 * time.Millisecond)
fmt.Println(i)
} }
} }
func main() { func main() {
c := make(chan int) //no capacity, unbuffered channel. This means that sends to the channel will block until another goroutine is ready to receive from the channel, and receives from the channel will block until another goroutine sends a value to the channel. c := make(chan int)
// This is useful for synchronizing between goroutines, as it ensures that the sender and receiver are coordinated. done := make(chan bool)
go func() { print(c); done <- true; close(done) }()
// Use a channel named done to signal when the printChannel goroutine has finished processing the input channel.
// This allows us to wait for the goroutine to complete before exiting the main function.
//done := make(chan bool)
//go printChannel(c, done)
// Use a WaitGroup to wait for the printWaitgroup goroutine to finish instead of using a done channel.
wg := sync.WaitGroup{}
wg.Add(1)
go printWaitgroup(c, &wg)
c <- 1 c <- 1
c <- 2 c <- 2
c <- 3 c <- 3
close(c) // Close the channel to signal to the printChannel goroutine that there are no more values to read. close(c)
// This will cause the for loop in printChannel to exit and the function to send a value to the done channel, allowing the main function to wait for it to finish. <-done
//<-done //alternative to using a done channel, we can wait for the WaitGroup to finish instead.
wg.Wait() // Wait for the printWaitgroup goroutine to finish before exiting the main function.
} }

View File

@ -8,144 +8,36 @@ import (
"time" "time"
) )
/* func mergeSortSequential(data []float64) {
* if len(data) <= 1 {
* Sorts a slice of float64 values using the merge sort algorithm in parallel with channels. return
* }
* @param data The slice of float64 values to be sorted.
* @param sorted The output channel where the sorted values will be sent. This channel should be buffered to avoid blocking. mid := len(data) / 2
* The function will close this channel when sorting is complete. left := data[:mid]
*/ right := data[mid:]
func mergeSortChannelsInternal(data []float64, sorted chan<- float64) {
mergeSortSequential(left)
// close the sorted channel when we're done sending sorted values. This ensures that the receiver can detect when all sorted values have been sent and can stop waiting for more data. mergeSortSequential(right)
defer close(sorted)
copy(data, merge(left, right))
if len(data) <= 10000 { }
// For small slices, sort them sequentially to avoid the overhead of creating goroutines and channels. This threshold is chosen to balance the overhead of concurrency with the benefits of parallelism.
sortedData := make([]float64, len(data)) func mergeSortParallel(data []float64) {
copy(sortedData, data) if len(data) <= 1000 {
mergeSortSequential(sortedData) mergeSortSequential(data)
// Send sorted values to the output channel. We iterate over the sorted slice and send each value to the sorted channel.
for _, x := range sortedData {
sorted <- x
}
} else {
// For larger slices, split them into two halves and sort each half in parallel using channels.
mid := len(data) / 2
left := data[:mid]
right := data[mid:]
sortedLeft := make(chan float64, 1000)
sortedRight := make(chan float64, 1000)
go mergeSortChannelsInternal(left, sortedLeft)
go mergeSortChannelsInternal(right, sortedRight)
// Merge the sorted halves and send the merged sorted values to the output channel.
mergeChannels(sortedLeft, sortedRight, sorted)
}
}
/**
* Merges two sorted channels into a single sorted channel.
*
* @param a The first sorted channel.
* @param b The second sorted channel.
* @param out The output channel where the merged sorted values will be sent. This channel should be buffered to avoid blocking.
*/
func mergeChannels(a, b <-chan float64, out chan<- float64) {
var okA, okB bool
var valA, valB float64
for {
// If we don't have a value from channel a, try to read from it.
if !okA {
valA, okA = <-a
}
// If we don't have a value from channel b, try to read from it.
if !okB {
valB, okB = <-b
}
// If both channels are closed and we've read all values, break the loop.
if !okA && !okB {
break
}
// If one channel is closed, we can only read from the other channel.
if !okA {
out <- valB
okB = false
continue
}
if !okB {
out <- valA
okA = false
continue
}
// Both channels have values, compare them and send the smaller one to the output channel.
if valA < valB {
out <- valA
okA = false
} else {
out <- valB
okB = false
}
}
}
/**
* Sorts a slice of float64 values using the merge sort algorithm in parallel with channels.
*
* @param data The slice of float64 values to be sorted. The sorting is done in-place, so the original slice will be modified.
*/
func mergeSortChannels(data []float64) {
sorted := make(chan float64, 1000)
go mergeSortChannelsInternal(data, sorted)
sortedData := make([]float64, len(data))
i := 0
for x := range sorted {
sortedData[i] = x
i++
}
copy(data, sortedData)
}
/**
* Sorts a slice of float64 values using the merge sort algorithm in parallel.
*
* @param data The slice of float64 values to be sorted. The sorting is done in-place, so the original slice will be modified.
*/
func mergeSortParallel(data []float64) {
// Base case: if the slice has 10000 or fewer elements, sort it sequentially. This threshold is chosen to balance
// the overhead of creating goroutines with the benefits of parallelism. For small slices, the overhead of goroutines
// may outweigh the performance gains, so we sort them sequentially.
if len(data) <= 10000 {
mergeSortSequential(data)
return return
} }
// Split the slice into two halves. The mid index is calculated as the length of the data divided by 2, which gives us the midpoint of the slice.
mid := len(data) / 2 mid := len(data) / 2
left := data[:mid] left := data[:mid]
right := data[mid:] right := data[mid:]
// Use a WaitGroup to wait for both halves to be sorted before merging. The WaitGroup allows us to synchronize the completion
// of the two goroutines that will sort the left and right halves in parallel.
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
// Sort left and right halves in parallel using goroutines. Each goroutine will call mergeSortParallel
// on its respective half and then signal when it's done using the WaitGroup.
go func() { mergeSortParallel(left); wg.Done() }() go func() { mergeSortParallel(left); wg.Done() }()
go func() { mergeSortParallel(right); wg.Done() }() go func() { mergeSortParallel(right); wg.Done() }()
wg.Wait() wg.Wait()
@ -153,57 +45,46 @@ func mergeSortParallel(data []float64) {
copy(data, merge(left, right)) copy(data, merge(left, right))
} }
/** func mergeSortChannelsInternal(data []float64, sorted chan<- float64) {
* Sorts a slice of float64 values using the merge sort algorithm sequentially.
*
* @param data The slice of float64 values to be sorted. The sorting is done in-place, so the original slice will be modified.
*/
func mergeSortSequential(data []float64) {
// Base case: if the slice has 0 or 1 element, it's already sorted. if len(data) < 1000 {
if len(data) <= 1 { sortedData := make([]float64, len(data))
return copy(sortedData, data)
mergeSortSequential(sortedData)
for _, := 0; i < count; i++ {
}
close(sorted)
} }
// Recursive case: split the slice into two halves, ...
mid := len(data) / 2
left := data[:mid]
right := data[mid:]
// ... sort each half recursively, ...
mergeSortSequential(left)
mergeSortSequential(right)
// ... and then merge the sorted halves back together.
copy(data, merge(left, right))
} }
/** func mergeSortChannels(data []float64) {
* Merges two sorted slices into a single sorted slice. sorted := make(chan float64)
* go mergeSortChannelsInternal(data, sorted)
* @param left The first sorted slice. sortedData := make([]float64, len(data))
* @param right The second sorted slice. i := 0
* @return A new slice containing all elements from both input slices, sorted in ascending order. for x := range sorted {
*/ sortedData[i] = x
i++
}
copy(data, sortedData)
}
func merge(left, right []float64) []float64 { func merge(left, right []float64) []float64 {
// Create a new slice to hold the merged result. The capacity is set to the sum of the lengths of left and right for efficiency.
result := make([]float64, 0, len(left)+len(right)) result := make([]float64, 0, len(left)+len(right))
i, j := 0, 0 i, j := 0, 0
// Merge elements from left and right until one of the slices is exhausted.
for i < len(left) && j < len(right) { for i < len(left) && j < len(right) {
if left[i] < right[j] { if left[i] < right[j] {
// Append the smaller element from left to the result and move the pointer i.
result = append(result, left[i]) result = append(result, left[i])
i++ i++
} else { } else {
// Append the smaller element from right to the result and move the pointer j.
result = append(result, right[j]) result = append(result, right[j])
j++ j++
} }
} }
// Append any remaining elements from left or right. The ... operator is used to expand the slice into individual elements.
result = append(result, left[i:]...) result = append(result, left[i:]...)
result = append(result, right[j:]...) result = append(result, right[j:]...)
@ -213,7 +94,7 @@ func merge(left, right []float64) []float64 {
func main() { func main() {
//1. DATA CREATION //1. DATA CREATION
data := make([]float64, 1000*1000*20) //*1000*20) data := make([]float64, 1000*1000*20)
for i := range data { for i := range data {
data[i] = rand.Float64() * 100 // Random floats between 0 and 100 data[i] = rand.Float64() * 100 // Random floats between 0 and 100
@ -227,8 +108,7 @@ func main() {
start := time.Now() start := time.Now()
//mergeSortSequential(data) //mergeSortSequential(data)
//mergeSortParallel(data) mergeSortParallel(data)
mergeSortChannels(data)
elapsed := time.Since(start) elapsed := time.Since(start)
fmt.Printf("MergeSort took %s\n", elapsed) fmt.Printf("MergeSort took %s\n", elapsed)

View File

@ -12,19 +12,15 @@ func main() {
ch2 := make(chan string) ch2 := make(chan string)
go func() { go func() {
// Simulate some work by sleeping for a random duration between 1 and 5 seconds before sending a message to ch1.
time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second) time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second)
ch1 <- "Message from channel 1" ch1 <- "Message from channel 1"
}() }()
go func() { go func() {
// Simulate some work by sleeping for a random duration between 1 and 5 seconds before sending a message to ch2.
time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second) time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second)
ch2 <- "Message from channel 2" ch2 <- "Message from channel 2"
}() }()
// Create multiple dynamic channels and send messages to them in separate goroutines.
// This demonstrates how we can use select to wait for messages from a variable number of channels.
count := 10 count := 10
var channels []chan string var channels []chan string
for i := range count { for i := range count {
@ -32,31 +28,14 @@ func main() {
channels = append(channels, ch) channels = append(channels, ch)
go func() { go func() {
time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second) time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second)
ch <- fmt.Sprintf("Message from dynamic channel %d", i) ch <- fmt.Sprintln("Message from Channel %d", i)
}() }()
} }
// Fan-in pattern: Create a single channel to receive messages from multiple channels. fanIn := make(chan string, count)
// This allows us to use select to wait for messages from all channels in a single case. for i := range count {
fanIn := make(chan string) go func() { fanIn <- <-channels[i] }()
for _, ch := range channels {
go func(c chan string) {
fanIn <- <-c // Read a message from the dynamic channel and send it to the fanIn channel. A temporary variable is needed to capture the current value of ch.
}(ch)
} }
println("Recieved: ", <-fanIn)
// Use select to wait for messages from multiple channels. The select statement allows us to wait on multiple channel operations
// and proceed with the one that is ready first.
select {
case msg1 := <-ch1:
println("Received:", msg1)
case msg2 := <-ch2:
println("Received:", msg2)
case msg := <-fanIn:
println("Received:", msg)
case <-time.After(3 * time.Second): // The time.After function returns a channel that will send the current time after the specified duration. This case will be selected if no other channels are ready within 3 seconds, allowing us to handle a timeout scenario.
println("Timeout occurred")
//default: // The default case is executed if none of the channels are ready. Not to be used in this example, as it would cause the select statement to proceed immediately.
// println("Nothing")
}
} }

View File

@ -1,63 +0,0 @@
# Assignment - Airport Simulation
This go-program is a simulation of the departure process of an airport that consists of *aircrafts*, *passengers*, *checkin-counter*, *security-checks*, *gates*, a *baggage handling system*, a *runway*, a *flight schedule* and *gates*. Passengers go through the following steps:
- **Checkin**: The ticket is verified, baggage is checked in and handed over to the baggage handling system, and a boarding pass is issued.
- **Security-Check**: The boarding pass is verified and the passenger is checked for prohibited items.
- **Gate**: The passenger walks to the correct gate and waits there until boarding time. The gate takes then care of boarding the aircraft, while simultaneously loading baggage into the aircraft.
Each step has a processing time and there are several waiting times in between.
Aircrafts arrive at the gate on boarding time and leave to the runway on departure time according to the flight schedule.
## Task
Your task is to complete the implementation by going through the **TODO** statements in the files `baggageHandlingSystem.go`, `securityCheck.go`, and `gate.go`. All other files already contain an implementation.
**Hint**: The expected behavior is defined in detail in the corresponding unit tests.
**Hint**: Be aware of synchronization issues between the different goroutines for all actors. Use `channels` whenever applicable. See `checkin.go` for inspiration.
## Example Output
```
Flight Number Destination Gate Boarding Time Departure Time
--------------------------------------------------------------------------------
AF2062 ZAG 7 13:59:27 13:59:57
BA8851 SOF 9 13:59:47 14:00:17
SK4243 FRA 5 14:01:46 14:02:16
KL5728 WAW 9 14:03:06 14:03:36
SK8183 PRG 8 14:03:12 14:03:42
AF8488 LIS 5 13:59:45 14:00:15
KL5937 RIX 1 14:01:26 14:01:56
AY1065 BRU 7 14:00:37 14:01:07
BA1529 ZRH 5 14:02:03 14:02:33
IB2405 IST 3 14:01:51 14:02:21
What/Who arrives at airport? (p for passenger, c for car, t for train, q to quit): p
stoic_dijkstra has arrived at the airport for the flight number IB2405 to IST in 3.11 minutes with 2 bags and carry-on items: [Medicine Knife Sharpener]
stoic_dijkstra completed the checkin procedure and is now heading to security
stoic_dijkstra has successfully passed the security check and is now walking to the gate
elated_hellman has arrived at the airport for the flight number KL5728 to WAW in 4.19 minutes with 2 bags and carry-on items: [Explosives]
vibrant_maxwell has arrived at the airport for the flight number BA8851 to SOF in 0.88 minutes with 2 bags and carry-on items: [Keys Matches]
dazzling_lewin has arrived at the airport for the flight number AF2062 to ZAG in 0.54 minutes with 1 bags and carry-on items: []
nifty_hermann has arrived at the airport for the flight number IB2405 to IST in 2.94 minutes with 1 bags and carry-on items: [Resistance Bands Gloves]
elated_hellman completed the checkin procedure and is now heading to security
vibrant_maxwell completed the checkin procedure and is now heading to security
elated_hellman: prohibited item detected: [Explosives]
flight AF2062 is ready for boarding
dazzling_lewin completed the checkin procedure and is now heading to security
vibrant_maxwell has successfully passed the security check and is now walking to the gate
nifty_hermann completed the checkin procedure and is now heading to security
dazzling_lewin has successfully passed the security check and is now walking to the gate
nifty_hermann has successfully passed the security check and is now walking to the gate
dazzling_lewin has successfully boarded flight AF2062 to ZAG
flight AF8488 is ready for boarding
flight BA8851 is ready for boarding
loaded baggage {[Goggles Fire Extinguisher Controller]} onto flight BA8851
loaded baggage {[Book Clipboard]} onto flight BA8851
vibrant_maxwell has successfully boarded flight BA8851 to SOF
boarding completed for flight number AF2062
Flight AF2062 is waiting for takeoff
Flight AF2062 to ZAG is taking off
Flight AF2062 to ZAG has taken off with 1 passengers and 0 bags on board
Closing airport
The following baggage was lost today: []
```

View File

@ -1,39 +0,0 @@
package airport
import (
"fmt"
)
type Aircraft struct {
Flight Flight
Passengers []Passenger
Baggage []Baggage
}
var _ BaggageProcessor = &Aircraft{}
func NewAircraft(flight Flight) Aircraft {
return Aircraft{flight, []Passenger{}, []Baggage{}}
}
func (ac *Aircraft) ProcessBaggage(fn FlightNumber, b Baggage) error {
if fn != ac.Flight.Id {
return fmt.Errorf("wrong flight %s", fn)
}
ac.Baggage = append(ac.Baggage, b)
fmt.Printf("loaded baggage %v onto flight %v\n", b, fn)
return nil
}
func (ac *Aircraft) Process(p Passenger) error {
if p.BoardingPass.FlightNumber != ac.Flight.Id {
return fmt.Errorf("%s is unauthorized to enter aircraft", p.Name)
}
ac.Passengers = append(ac.Passengers, p)
return nil
}
func (ac *Aircraft) Start(gate *Gate, rwy *Runway) {
gate.ProcessAircraft(ac)
rwy.ProcessAircraft(*ac)
}

View File

@ -1,62 +0,0 @@
package airport
import (
"time"
)
type Airport struct {
Checkin Checkin
Security SecurityCheck
Gates map[GateNumber]*Gate
Runway Runway
Bhs *BaggageHandlingSystem
FlightSchedule FlightSchedule
}
func DefaultAirport() Airport {
ap := Airport{
Security: NewSecurityCheck(time.Duration(1 * time.Second)),
Gates: NewGates(10),
Runway: NewRunway(time.Duration(3 * time.Second)),
}
fs := GenerateRandomFlightSchedule(ap.Gates, 10)
ap.FlightSchedule = fs
baggageSinks := make(map[FlightNumber](BaggageProcessor))
for _, g := range ap.Gates {
var flightsForGate []Flight
for _, f := range fs.AllFlights() {
if f.GateNumber == g.id {
flightsForGate = append(flightsForGate, f)
}
baggageSinks[f.Id] = g
}
g.SetFlights(flightsForGate)
ap.Gates[g.id] = g
}
bhs := NewBaggageHandlingSystem(time.Duration(1*time.Second), baggageSinks)
ap.Bhs = &bhs
ap.Checkin = NewCheckin(time.Duration(1*time.Second), &fs, &bhs)
return ap
}
func (a *Airport) Start() {
go a.Checkin.Start()
go a.Security.Start()
go a.Bhs.Start()
go a.Runway.Start()
for _, g := range a.Gates {
go g.Start()
}
}
func (a *Airport) NewPassenger() Passenger {
return NewRandomPassenger(a.FlightSchedule.AllFlights())
}

View File

@ -1,44 +0,0 @@
package airport
import (
"time"
)
type BaggageProcessor interface {
ProcessBaggage(FlightNumber, Baggage) error
}
type BaggageHandlingSystem struct {
processingTime time.Duration
sinks map[FlightNumber]BaggageProcessor
//TODO: extend
}
type LostBaggage struct {
Baggage
FlightNumber FlightNumber
Err error
}
var _ BaggageProcessor = &BaggageHandlingSystem{}
func NewBaggageHandlingSystem(processingTime time.Duration, sinks map[FlightNumber]BaggageProcessor) BaggageHandlingSystem {
return BaggageHandlingSystem{
processingTime: processingTime,
sinks: sinks,
}
}
func (bhs *BaggageHandlingSystem) ProcessBaggage(fn FlightNumber, b Baggage) error {
//TODO: implement
return nil
}
func (bhs *BaggageHandlingSystem) CollectLostBaggage() []LostBaggage {
//TODO: implement
return nil
}
func (bhs *BaggageHandlingSystem) Start() {
//TODO: implement
}

View File

@ -1,133 +0,0 @@
package airport_test
import (
"fmt"
"reflect"
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport"
)
type BaggageSpyData struct {
fn airport.FlightNumber
b airport.Baggage
t time.Time
}
type BaggageProcessorSpy struct {
Baggage []BaggageSpyData
Err error
}
func (bps *BaggageProcessorSpy) ProcessBaggage(fn airport.FlightNumber, b airport.Baggage) error {
bps.Baggage = append(bps.Baggage, BaggageSpyData{fn, b, time.Now()})
return bps.Err
}
var _ airport.BaggageProcessor = &BaggageProcessorSpy{}
func compareSink(t *testing.T, expected []airport.Baggage, expectedFlightNumber airport.FlightNumber, got BaggageProcessorSpy, start time.Time) {
for _, b := range got.Baggage {
if elapsed := b.t.Sub(start); elapsed < 1*time.Millisecond {
t.Errorf("processing time too short: got %v, want at least 1ms", elapsed)
}
if b.fn != expectedFlightNumber {
t.Errorf("expected flight number: %v, got: %v", expectedFlightNumber, b.fn)
}
}
if len(got.Baggage) != len(expected) {
t.Errorf("expected number of baggage: %d, got: %d", len(expected), len(got.Baggage))
}
deliveredBaggage := []airport.Baggage{}
for _, b := range got.Baggage {
deliveredBaggage = append(deliveredBaggage, b.b)
}
if !reflect.DeepEqual(deliveredBaggage, expected) {
t.Errorf("expected delivered baggage for %v: %v, got: %v", expectedFlightNumber, expected, deliveredBaggage)
}
}
func TestBaggageHandlingSystem(t *testing.T) {
sink123 := BaggageProcessorSpy{nil, nil}
sink456 := BaggageProcessorSpy{nil, fmt.Errorf("aircraft already gone")}
sinks := map[airport.FlightNumber]airport.BaggageProcessor{
airport.FlightNumber("LH123"): &sink123,
airport.FlightNumber("LH456"): &sink456,
}
sut := airport.NewBaggageHandlingSystem(1*time.Millisecond, sinks)
go sut.Start()
testCases := []struct {
name string
baggage airport.Baggage
fn airport.FlightNumber
expectErr bool
expectDeliveredBaggage123 []airport.Baggage
expectDeliveredBaggage456 []airport.Baggage
expectLostBaggage []airport.LostBaggage
}{
{
name: "Valid baggage",
baggage: airport.Baggage{[]string{"Socks", "Shirts"}},
fn: "LH123",
expectErr: false,
expectDeliveredBaggage123: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
expectDeliveredBaggage456: []airport.Baggage{},
expectLostBaggage: []airport.LostBaggage{},
},
{
name: "Aircraft already gone",
baggage: airport.Baggage{[]string{"Socks", "Shirts"}},
fn: "LH456",
expectErr: false,
expectDeliveredBaggage123: []airport.Baggage{},
expectDeliveredBaggage456: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
expectLostBaggage: []airport.LostBaggage{
{
airport.Baggage{[]string{"Socks", "Shirts"}},
"LH456",
fmt.Errorf("aircraft already gone"),
},
},
},
{
name: "Unknown flight",
baggage: airport.Baggage{[]string{"Socks", "Shirts"}},
fn: "XX999",
expectErr: true,
expectDeliveredBaggage123: []airport.Baggage{},
expectDeliveredBaggage456: []airport.Baggage{},
expectLostBaggage: []airport.LostBaggage{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
sink123.Baggage = []BaggageSpyData{}
sink456.Baggage = []BaggageSpyData{}
start := time.Now()
err := sut.ProcessBaggage(tc.fn, tc.baggage)
time.Sleep(10 * time.Millisecond) //not really threadsafe because the 10 milliseconds have never been guaranteed. TODO: replace by sync mechanism
if (err != nil) != tc.expectErr {
t.Errorf("expected error: %v, got: %v", tc.expectErr, err != nil)
}
compareSink(t, tc.expectDeliveredBaggage123, "LH123", sink123, start)
compareSink(t, tc.expectDeliveredBaggage456, "LH456", sink456, start)
lostBaggage := sut.CollectLostBaggage()
if !reflect.DeepEqual(lostBaggage, tc.expectLostBaggage) {
t.Errorf("expected lost baggage: %v, got: %v", tc.expectLostBaggage, lostBaggage)
}
})
}
}

View File

@ -1,72 +0,0 @@
package airport
import (
"errors"
"fmt"
"time"
)
type Checkin struct {
processingTime time.Duration
queue chan passengerCheckinData
flightProvider FlightProvider
baggageProcessor BaggageProcessor
}
type passengerCheckinData struct {
p *Passenger
err chan error
}
func NewCheckin(processingTime time.Duration, fp FlightProvider, bp BaggageProcessor) Checkin {
return Checkin{
processingTime: processingTime,
queue: make(chan passengerCheckinData),
flightProvider: fp,
baggageProcessor: bp,
}
}
func (c Checkin) Process(p *Passenger) error {
err := make(chan error)
c.queue <- passengerCheckinData{p: p, err: err}
return <-err
}
func (c Checkin) Start() {
defer fmt.Println("checkin terminated")
for element := range c.queue {
time.Sleep(c.processingTime)
//1. check ticket
if element.p.Name != element.p.Ticket.Name {
element.err <- fmt.Errorf("name %s on ticket does not match", element.p.Ticket.Name)
continue
}
flight, err := c.flightProvider.GetFlight(element.p.Ticket.FlightNumber)
if err != nil {
element.err <- err
continue
}
//2. issue boarding pass
element.p.BoardingPass = &BoardingPass{element.p.Ticket, flight.GateNumber}
//3. check in bags
var baggageErr error
for _, bag := range element.p.Bags {
if err := c.baggageProcessor.ProcessBaggage(flight.Id, bag); err != nil {
baggageErr = errors.Join(baggageErr, err)
}
}
if baggageErr != nil {
element.err <- baggageErr
continue
}
element.p.Bags = []Baggage{}
element.err <- nil
}
}

View File

@ -1,116 +0,0 @@
package airport_test
import (
"fmt"
"reflect"
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport"
)
type FlightProviderStub struct {
}
func (fps FlightProviderStub) GetFlight(fn airport.FlightNumber) (airport.Flight, error) {
if fn == "LH123" {
return airport.Flight{"LH123", "FRA", airport.GateNumber(1), time.Now(), time.Now()}, nil
}
return airport.Flight{}, fmt.Errorf("unknown flight %v", fn)
}
var _ airport.FlightProvider = FlightProviderStub{}
func TestCheckin(t *testing.T) {
fp := FlightProviderStub{}
bps := BaggageProcessorSpy{}
sut := airport.NewCheckin(1*time.Millisecond, &fp, &bps)
go sut.Start()
testCases := []struct {
name string
passenger airport.Passenger
expectErr bool
expectBaggage []airport.Baggage
expectBoardingpass *airport.BoardingPass
expectBPSBaggage []airport.Baggage
}{
{
name: "Valid passenger with valid flight",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
nil,
[]airport.Baggage{{[]string{"Socks", "Shirts"}}},
[]string{},
},
expectErr: false,
expectBaggage: []airport.Baggage{},
expectBoardingpass: &airport.BoardingPass{airport.Ticket{"Max Mustermann", "FRA", "LH123"}, airport.GateNumber(1)},
expectBPSBaggage: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
},
{
name: "Passenger with unknown flight",
passenger: airport.Passenger{
"John Doe",
airport.Ticket{"John Doe", "MUC", "LH999"},
nil,
[]airport.Baggage{{[]string{"Socks", "Shirts"}}},
[]string{},
},
expectErr: true,
expectBaggage: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
expectBoardingpass: nil,
expectBPSBaggage: []airport.Baggage{},
},
{
name: "Passenger with wrong ticket",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Someone else", "FRA", "LH123"},
nil,
[]airport.Baggage{{[]string{"Socks", "Shirts"}}},
[]string{},
},
expectErr: true,
expectBaggage: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
expectBoardingpass: nil,
expectBPSBaggage: []airport.Baggage{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
bps.Baggage = []BaggageSpyData{}
start := time.Now()
err := sut.Process(&tc.passenger)
if elapsed := time.Since(start); elapsed < 1*time.Millisecond {
t.Errorf("processing time too short: got %v, want at least 1ms", elapsed)
}
if (err != nil) != tc.expectErr {
t.Errorf("expected error: %v, got: %v", tc.expectErr, err != nil)
}
if !reflect.DeepEqual(tc.passenger.BoardingPass, tc.expectBoardingpass) {
t.Errorf("expected boarding pass: %v, got: %v", tc.expectBoardingpass, tc.passenger.BoardingPass)
}
if !reflect.DeepEqual(tc.passenger.Bags, tc.expectBaggage) {
t.Errorf("expected baggage: %v, got: %v", tc.expectBaggage, tc.passenger.Bags)
}
gotBPSBaggage := []airport.Baggage{}
for _, b := range bps.Baggage {
gotBPSBaggage = append(gotBPSBaggage, b.b)
}
if !reflect.DeepEqual(gotBPSBaggage, tc.expectBPSBaggage) {
t.Errorf("expected baggage processor baggage: %v, got: %v", tc.expectBPSBaggage, gotBPSBaggage)
}
})
}
}

View File

@ -1,104 +0,0 @@
package airport
import (
"fmt"
"math/rand"
"strings"
"time"
)
type FlightNumber string
type Flight struct {
Id FlightNumber
Destination string
GateNumber GateNumber
BoardingTime time.Time
DepartureTime time.Time
}
type FlightProvider interface {
GetFlight(FlightNumber) (Flight, error)
}
type FlightSchedule struct {
flights map[FlightNumber]Flight
}
var possibleDestinations = []string{
"AMS", "ATH", "BCN", "BRU", "BUD", "CPH", "DUB", "DUS", "EDI", "FCO",
"FRA", "GVA", "HEL", "IST", "LIS", "LYS", "MAD", "MUC", "OSL", "PRG",
"RIX", "SOF", "STO", "TLL", "VIE", "WAW", "ZAG", "ZRH", "LUX", "MLA",
}
func GenerateRandomFlightNumbers(count int) []FlightNumber {
airlines := []string{"LH", "AF", "KL", "BA", "SK", "AZ", "IB", "TK", "AY", "OS"}
flightNumbers := make([]FlightNumber, count)
for i := 0; i < count; i++ {
airline := airlines[rand.Intn(len(airlines))]
number := rand.Intn(9000) + 1000 // Random 4-digit number
flightNumbers[i] = FlightNumber(fmt.Sprintf("%s%04d", airline, number))
}
return flightNumbers
}
func GenerateRandomFlightSchedule(gates map[GateNumber]*Gate, count int) FlightSchedule {
flightNumbers := GenerateRandomFlightNumbers(count)
flights := make(map[FlightNumber]Flight)
gateNumbers := make([]GateNumber, 0, len(gates))
for gn, _ := range gates {
gateNumbers = append(gateNumbers, gn)
}
for _, flightNumber := range flightNumbers {
destination := possibleDestinations[rand.Intn(len(possibleDestinations))]
gateNumber := gateNumbers[rand.Intn(len(gateNumbers))]
departureTime := time.Now().Add(time.Duration(rand.Intn(5*60)) * time.Second)
boardingTime := departureTime.Add(-30 * time.Second)
flights[flightNumber] = Flight{
Id: flightNumber,
Destination: destination,
GateNumber: gateNumber,
BoardingTime: boardingTime,
DepartureTime: departureTime,
}
}
return FlightSchedule{flights: flights}
}
func (fs *FlightSchedule) AllFlights() []Flight {
flights := make([]Flight, 0, len(fs.flights))
for _, flight := range fs.flights {
flights = append(flights, flight)
}
return flights
}
func (fs *FlightSchedule) GetFlight(flightNumber FlightNumber) (Flight, error) {
if flight, ok := fs.flights[flightNumber]; ok {
return flight, nil
} else {
return Flight{}, fmt.Errorf("invalid flight number %v", flightNumber)
}
}
func (fs *FlightSchedule) Print() {
fmt.Printf("%-15s %-15s %-10s %-20s %-20s\n", "Flight Number", "Destination", "Gate", "Boarding Time", "Departure Time")
fmt.Println(strings.Repeat("-", 80))
for _, flight := range fs.AllFlights() {
fmt.Printf("%-15s %-15s %-10d %-20s %-20s\n",
flight.Id,
flight.Destination,
flight.GateNumber,
flight.BoardingTime.Format("15:04:05"),
flight.DepartureTime.Format("15:04:05"),
)
}
}

View File

@ -1,63 +0,0 @@
package airport
import (
"math/rand"
"time"
)
var distantFuture = time.Unix(1<<63-1, 0) // Max value for Unix timestamp
type GateNumber int
type Gate struct {
id GateNumber
walkingTimeFromSecurity time.Duration
taxiTimeToRunway time.Duration
//TODO: extend
}
var _ BaggageProcessor = &Gate{}
func NewGate(id GateNumber, walkingTimeFromSecurity time.Duration, taxiTimeToRunway time.Duration) Gate {
return Gate{id,
walkingTimeFromSecurity,
taxiTimeToRunway,
}
}
func NewGates(count int) map[GateNumber]*Gate {
gates := make(map[GateNumber]*Gate)
for i := range count {
walkingTime := time.Duration(5+rand.Intn(6)) * time.Second
taxiTime := time.Duration(5+rand.Intn(6)) * time.Second
gate := NewGate(GateNumber(i+1), walkingTime, taxiTime)
gates[GateNumber(i+1)] = &gate
}
return gates
}
func (g *Gate) SetFlights(flights []Flight) {
//TODO: implement
}
// Blocks for the walking distance to the gate + the time until the passenger has boarded
func (g *Gate) Process(passenger Passenger) error {
//TODO: implement
return nil
}
// Blocks until departure (includes taxi time to runway)
func (g *Gate) ProcessAircraft(ac *Aircraft) {
//TODO: implement
}
func (g *Gate) ProcessBaggage(fn FlightNumber, b Baggage) error {
//TODO: implement
return nil
}
func (g Gate) Start() {
//TODO: implement
}

View File

@ -1,112 +0,0 @@
package airport_test
import (
"reflect"
"sync"
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport"
)
func TestGateSingleFlight(t *testing.T) {
sut := airport.NewGate(airport.GateNumber(25), time.Duration(3*time.Millisecond), time.Duration(1*time.Millisecond))
flight := airport.Flight{"LH123", "FRA", 25, time.Now().Add(5 * time.Millisecond), time.Now().Add(10 * time.Millisecond)}
sut.SetFlights([]airport.Flight{
flight,
})
go sut.Start()
start := time.Now()
wg := sync.WaitGroup{}
//the aircraft
wg.Add(1)
ac := airport.NewAircraft(flight)
departureTime := time.Now()
go func() { sut.ProcessAircraft(&ac); departureTime = time.Now(); wg.Done() }()
//the baggage
wg.Add(1)
baggage := airport.Baggage{[]string{"socks", "shirt"}}
var bag1err error
go func() { bag1err = sut.ProcessBaggage("LH123", baggage); wg.Done() }()
//a passenger arriving on time
wg.Add(1)
ticket1 := airport.Ticket{"John Doe", "FRA", "LH123"}
pax1 := airport.Passenger{"John Doe", ticket1, &airport.BoardingPass{ticket1, 25}, []airport.Baggage{}, []string{}}
pax1boardingTime := time.Now()
var pax1err error
go func() { pax1err = sut.Process(pax1); pax1boardingTime = time.Now(); wg.Done() }()
//another passenger arriving too late
wg.Add(1)
ticket2 := airport.Ticket{"Jane Doe", "FRA", "LH123"}
pax2 := airport.Passenger{"Jane Doe", ticket2, &airport.BoardingPass{ticket2, 25}, []airport.Baggage{}, []string{}}
var pax2err error
go func() { time.Sleep(8 * time.Millisecond); pax2err = sut.Process(pax2); wg.Done() }()
//another passenger arriving sometime during boarding
wg.Add(1)
ticket3 := airport.Ticket{"Alex Smith", "FRA", "LH123"}
pax3 := airport.Passenger{"Alex Smith", ticket3, &airport.BoardingPass{ticket3, 25}, []airport.Baggage{}, []string{}}
pax3boardingTime := time.Now()
var pax3err error
go func() {
time.Sleep(6 * time.Millisecond)
pax3err = sut.Process(pax3)
pax3boardingTime = time.Now()
wg.Done()
}()
//------------------------FROM HERE: checking ----------------------------------
wg.Wait()
//--------------------------check aircraft-------------------
if elapsed := departureTime.Sub(start); elapsed < 11*time.Millisecond {
t.Errorf("departure time too short: got %v, want at least 11ms", elapsed)
}
expectedBoardedPax := []airport.Passenger{pax1, pax3}
if !reflect.DeepEqual(expectedBoardedPax, ac.Passengers) {
t.Errorf("boarded passengers mismatch: got %v, want %v", ac.Passengers, expectedBoardedPax)
}
expectedBaggageOnboard := []airport.Baggage{baggage}
if !reflect.DeepEqual(expectedBaggageOnboard, ac.Baggage) {
t.Errorf("baggage on board mismatch: got %v, want %v", ac.Baggage, expectedBaggageOnboard)
}
//----------------------------check baggage--------------------
if bag1err != nil {
t.Errorf("baggage encountered an error during processing: %v", bag1err)
}
//--------------------------check passengers-------------------
//boarding can only happen between boarding (5ms from start) and departure (10ms from start)
if elapsed := pax1boardingTime.Sub(start); elapsed < 5*time.Millisecond || elapsed > 10*time.Millisecond {
t.Errorf("passenger 1 boarding time out of range: got %v, want between 5ms and 10ms", elapsed)
}
if pax1err != nil {
t.Errorf("passenger 1 encountered an error during boarding: %v", pax1err)
}
if pax2err == nil {
t.Errorf("passenger 2 did not encounter an error during boarding, but was expected to")
}
//boarding can only happen after boarding (5ms from start)
if elapsed := pax3boardingTime.Sub(start); elapsed < 5*time.Millisecond {
t.Errorf("passenger 3 boarding time out of range: got %v, want more than 5ms", elapsed)
}
if pax3err != nil {
t.Errorf("passenger 3 encountered an error during boarding: %v", pax3err)
}
}

View File

@ -1,128 +0,0 @@
package airport
import (
"fmt"
"math/rand"
"time"
"github.com/docker/docker/pkg/namesgenerator"
)
type Ticket struct {
Name string
Destination string
FlightNumber FlightNumber
}
type BoardingPass struct {
Ticket
Gate GateNumber
}
type Baggage struct {
Items []string
}
type Passenger struct {
Name string
Ticket Ticket
BoardingPass *BoardingPass
Bags []Baggage
CarryOnItems []string
}
var PossibleItems = []string{
"Laptop", "Tablet", "Smartphone", "Headphones", "Charger", "Book", "Magazine", "Notebook", "Pen", "Pencil", "Carabiner",
"Water Bottle", "Snacks", "Sandwich", "Camera", "Tripod", "Gun", "Clothes", "Shoes", "Toothbrush", "Toothpaste",
"Shampoo", "Conditioner", "Soap", "Deodorant", "Perfume", "Makeup Kit", "Hairbrush", "Comb", "Razor", "Shaving Cream",
"First Aid Kit", "Medicine", "Umbrella", "Hat", "Sunglasses", "Scarf", "Gloves", "Wallet", "Passport", "Camping Gear",
"Keys", "Watch", "Jewelry", "Flashlight", "Power Bank", "Knife", "USB Drive", "External Hard Drive", "Gaming Console", "Controller",
"Board Game", "Puzzle", "Toy", "Explosives", "Stuffed Animal", "Blanket", "Pillow", "Sleeping Bag", "Towel", "Swimsuit",
"Goggles", "Flip Flops", "Backpack", "Handbag", "Suitcase", "Briefcase", "Binoculars", "Map", "Guidebook", "Notebook",
"Sketchpad", "Paints", "Brushes", "Musical Instrument", "Sheet Music", "Sports Equipment", "Yoga Mat", "Jump Rope", "Resistance Bands",
"Tent", "Cooking Utensils", "Portable Stove", "Food Containers", "Water Filter", "Fishing Rod", "Knife Sharpener", "Multi-tool", "Rope",
"Compass", "GPS Device", "Safety Vest", "Helmet", "Protective Gear", "Fire Extinguisher", "Matches", "Lighter", "Candles", "Notebook",
"Clipboard", "Calculator", "Documents", "Folders", "Envelopes",
}
func NewRandomPassenger(flights []Flight) Passenger {
// Generate random ticket details
name := namesgenerator.GetRandomName(0)
flight := flights[rand.Intn(len(flights))]
ticket := Ticket{
Name: name,
Destination: flight.Destination,
FlightNumber: flight.Id,
}
// Generate random baggage
numBags := rand.Intn(3) // Random number of bags (0 to 2)
bags := make([]Baggage, numBags)
for i := 0; i < numBags; i++ {
numItems := rand.Intn(5) + 1 // Random number of items (1 to 5)
items := make([]string, numItems)
for j := 0; j < numItems; j++ {
items[j] = PossibleItems[rand.Intn(len(PossibleItems))]
}
bags[i] = Baggage{
Items: items,
}
}
// Generate random carry-on items
numCarryOnItems := rand.Intn(3) // Random number of carry-on items (0 to 2)
carryOnItems := make([]string, numCarryOnItems)
for i := 0; i < numCarryOnItems; i++ {
carryOnItems[i] = PossibleItems[rand.Intn(len(PossibleItems))]
}
return Passenger{
Name: name,
Ticket: ticket,
BoardingPass: nil,
Bags: bags,
CarryOnItems: carryOnItems,
}
}
func (p *Passenger) Start(ap *Airport) {
fmt.Printf("%s has arrived at the airport for the flight number %s to %s in %.02f minutes with %d bags and carry-on items: %v\n",
p.Name,
p.Ticket.FlightNumber,
p.Ticket.Destination,
time.Until(ap.FlightSchedule.flights[p.Ticket.FlightNumber].DepartureTime).Minutes(),
len(p.Bags),
p.CarryOnItems)
err := ap.Checkin.Process(p)
if err != nil {
fmt.Printf("\033[31m%s: %v\033[0m\n", p.Name, err)
return
}
fmt.Printf("%s completed the checkin procedure and is now heading to security\n", p.Name)
err = ap.Security.Process(*p)
if err != nil {
fmt.Printf("\033[31m%s: %v\033[0m\n", p.Name, err)
return
}
fmt.Printf("%s has successfully passed the security check and is now walking to the gate\n", p.Name)
gate, ok := ap.Gates[p.BoardingPass.Gate]
if !ok {
fmt.Printf("\033[31m%s: invalid gate number %v\033[0m\n", p.Name, p.BoardingPass.Gate)
return
}
err = gate.Process(*p)
if err != nil {
fmt.Printf("\033[31mError: %s: %v\033[0m\n", p.Name, err)
return
}
fmt.Printf("\033[32m%s has successfully boarded flight %s to %s\033[0m\n", p.Name, p.Ticket.FlightNumber, p.Ticket.Destination)
}

View File

@ -1,33 +0,0 @@
package airport
import (
"fmt"
"time"
)
type Runway struct {
takeoffDuration time.Duration
queue chan Aircraft
}
func NewRunway(takeoffDuration time.Duration) Runway {
return Runway{
takeoffDuration: takeoffDuration,
queue: make(chan Aircraft),
}
}
func (r *Runway) Start() {
defer fmt.Println("runway terminated")
for ac := range r.queue {
fmt.Printf("Flight %v to %v is taking off\n", ac.Flight.Id, ac.Flight.Destination)
time.Sleep(r.takeoffDuration)
fmt.Printf("\033[32mFlight %v to %v has taken off with %v passengers and %v bags on board\033[0m\n", ac.Flight.Id, ac.Flight.Destination, int(len(ac.Passengers)), int(len(ac.Baggage)))
}
}
func (r *Runway) ProcessAircraft(ac Aircraft) {
fmt.Printf("Flight %v is waiting for takeoff\n", ac.Flight.Id)
r.queue <- ac
}

View File

@ -1,32 +0,0 @@
package airport
import (
"time"
)
var prohibitedItems = map[string]bool{"Knife": true, "Gun": true, "Explosives": true}
type SecurityCheck struct {
processingTime time.Duration
//TODO: extend
}
func NewSecurityCheck(processingTime time.Duration) SecurityCheck {
return SecurityCheck{
processingTime: processingTime,
}
}
// processes one passenger at a time. Each passenger must at least spend the processingTime at the security check
func (sc *SecurityCheck) Start() {
//TODO: implement
}
// returns an error if
// - the passenger has no boarding pass
// - the passenger's boardings pass does not match the name
// - the passenger carries a prohibited item
func (sc SecurityCheck) Process(p Passenger) error {
//TODO: implement
return nil
}

View File

@ -1,81 +0,0 @@
package airport_test
import (
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport"
)
func TestSecurityCheck(t *testing.T) {
sut := airport.NewSecurityCheck(1 * time.Millisecond)
go sut.Start()
testCases := []struct {
name string
passenger airport.Passenger
expectErr bool
}{
{
name: "Valid passenger",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
&airport.BoardingPass{airport.Ticket{"Max Mustermann", "FRA", "LH123"}, airport.GateNumber(1)},
[]airport.Baggage{},
[]string{"Laptop", "Hat"},
},
expectErr: false,
},
{
name: "Passenger without boarding pass",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
nil,
[]airport.Baggage{},
[]string{"Laptop", "Hat"},
},
expectErr: true,
},
{
name: "Passenger with wrong boarding pass",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
&airport.BoardingPass{airport.Ticket{"Someone else", "FRA", "LH123"}, airport.GateNumber(1)},
[]airport.Baggage{},
[]string{"Laptop", "Hat"},
},
expectErr: true,
},
{
name: "Passenger with prohibited item",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
&airport.BoardingPass{airport.Ticket{"Max Mustermann", "FRA", "LH123"}, airport.GateNumber(1)},
[]airport.Baggage{},
[]string{"Laptop", "Hat", "Knife"},
},
expectErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
start := time.Now()
err := sut.Process(tc.passenger)
if elapsed := time.Since(start); elapsed < 1*time.Millisecond {
t.Errorf("processing time too short: got %v, want at least 1ms", elapsed)
}
if (err != nil) != tc.expectErr {
t.Errorf("expected error: %v, got: %v", tc.expectErr, err != nil)
}
})
}
}

View File

@ -1,5 +0,0 @@
module gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport
go 1.25.0
require github.com/docker/docker v28.3.3+incompatible

View File

@ -1,2 +0,0 @@
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=

View File

@ -1,57 +0,0 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
"gitty.informatik.hs-mannheim.de/steger/pr3-code/go/06-airport/airport"
)
func main() {
ap := airport.DefaultAirport()
ap.FlightSchedule.Print()
defer func() {
lostBaggage := ap.Bhs.CollectLostBaggage()
fmt.Println("The following baggage was lost today: ", lostBaggage)
}()
ap.Start()
for _, flight := range ap.FlightSchedule.AllFlights() {
ac := airport.NewAircraft(flight)
gate := ap.Gates[flight.GateNumber]
go ac.Start(gate, &ap.Runway)
}
reader := bufio.NewReader(os.Stdin)
fmt.Print("What/Who arrives at airport? (p for passenger, c for car, t for train, q to quit): ")
for {
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
switch input {
case "q":
fmt.Println("Closing airport")
return
case "p":
p := ap.NewPassenger()
go p.Start(&ap)
case "c":
for i := 0; i < 4; i++ {
p := ap.NewPassenger()
go p.Start(&ap)
}
case "t":
for i := 0; i < 50; i++ {
p := ap.NewPassenger()
go p.Start(&ap)
}
default:
fmt.Println("Invalid command.")
}
}
}

View File

@ -1,48 +0,0 @@
FROM ubuntu:26.04
RUN apt-get update && apt-get install -y \
build-essential \
curl \
libffi-dev \
libffi8ubuntu1 \
libgmp-dev \
libgmp10 \
libncurses-dev \
git \
zlib1g-dev \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y \
locales \
&& locale-gen en_US.UTF-8 \
&& update-locale LANG=en_US.UTF-8 \
&& rm -rf /var/lib/apt/lists/*
USER ubuntu
ENV BOOTSTRAP_HASKELL_NONINTERACTIVE=1 \
BOOTSTRAP_HASKELL_INSTALL_HLS=1 \
BOOTSTRAP_HASKELL_GHC_VERSION=9.10.3 \
BOOTSTRAP_HASKELL_CABAL_VERSION=3.16.1.0
RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh \
&& ~/.ghcup/bin/ghcup gc -c -t \
&& rm -rf ~/.ghcup/cache/* \
&& find ~/.ghcup/ -name "*.tar.xz" -delete \
&& find ~/.ghcup/ -name "*.tar.gz" -delete \
&& find ~/.ghcup/ -type d -name "doc" -exec rm -rf {} + \
&& find ~/.ghcup/ -name "*_p.a" -delete \
&& rm -rf ~/.cabal/packages/*
ENV PATH="$PATH:/home/ubuntu/.ghcup/bin"
RUN cabal update && cabal install --lib HUnit \
&& rm -rf ~/.cabal/packages/*
# workaround for aarch64 architecture to compile HLS
RUN if [ "$(uname -m)" = "aarch64" ]; then \
export LANG=C.UTF-8 && export LC_ALL=C.UTF-8 \
&& cabal update && ghcup compile hls --version 2.14.0.0 --ghc 9.10.3 \
&& rm -rf ~/.cabal/packages/*; \
fi

View File

@ -1,15 +0,0 @@
{
"name": "Haskell Dev Container",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"extensions": [
"haskell.haskell"
]
}
},
"remoteUser": "ubuntu",
"updateRemoteUserUID": true
}

View File

@ -1,3 +0,0 @@
{
"editor.formatOnSave": true
}

View File

@ -1,61 +0,0 @@
{
// Automatically created by phoityne-vscode extension.
"version": "2.0.0",
"presentation": {
"reveal": "always",
"panel": "new"
},
"tasks": [
{
// F7
"group": {
"kind": "build",
"isDefault": false
},
"label": "haskell build",
"type": "shell",
"command": "cabal configure && cabal build"
},
{
// F6
"group": "build",
"type": "shell",
"label": "haskell clean & build",
"command": "cabal clean && cabal configure && cabal build"
},
{
// F8
"group": {
"kind": "test",
"isDefault": true
},
"type": "shell",
"label": "haskell test",
"command": "cabal test"
},
{
// F6
"isBackground": true,
"type": "shell",
"label": "haskell watch",
"command": "stack build --test --no-run-tests --file-watch"
}
,
{
// Build a single Haskell file
"type": "shell",
"label": "haskell build single file",
"command": "ghc",
"args": [ "${file}" ],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -1,14 +0,0 @@
# Build
`ghc hello-world.hs`
# Run
`runghc hello-world.hs`
# Run interactively
```
ghci
ghci> :load hello-world.hs
ghci> main
```

View File

@ -1,2 +0,0 @@
main :: IO ()
main = putStrLn "Hello, World!"

View File

@ -1,131 +0,0 @@
# Haskell - Expressions
The following illustrates some haskell basics. It can be reproduced using `ghci`.
## Types, Literals, and Constants along with Arithmetic and Boolean Operations
Expression | Type (:t) | Value | Comment
--- | --- | --- | --------
`2` | `Num a => a` | `2` |
`2.03` | `Fractional a => a` | `2.03` |
`'c'` | `Char` | `'c'`
`"hallo"` | `String` | `"hallo"` |
`True` | `Bool` | `True` |
`x=5` | `Num a => a` | `5` | Constant named `x`
`2 + 3` | `Num a => a` | `5` | Similar `-,*,^`
`2 / 3` | `Fractional a => a` | `0.6666666666666666` |
`1 > 2` | `Bool` | `False` | Similar `<, <=, >=, ==, /=`
`True && False` | `Bool` | `False` | Similar `\|\|,not`
`((2+3)*5 > 1) \|\| not (1 > 2)` | `Bool` | `True` | Right part is not evaluated due to lazy evaluation
## Unary Functions
Expression | Type (:t) | Value | Comment
--- | --- | --- | --------
`sqrt(4)` | `Floating a => a` | `2.0` |
`sqrt 4` | `Floating a => a` | `2.0` | Similar `abs,negate,signum,recip`
`sqrt` | `Floating a => a -> a` | |
`sqrt 3^2 + 4^2` | `Floating a => a` | `19.0` |
`sqrt (3^2 + 4^2)` | `Floating a => a` | `5.0` |
`negate(sqrt (3^2 + 4^2))` | `Floating a => a` | `-5.0` |
`negate $ sqrt (3^2 + 4^2)` | `Floating a => a`| `-5.0` | Function **application** operator `$`
`(negate . sqrt) (3^2 + 4^2)`| `Floating a => a`| `-5.0` | Function **composition** operator `.` Reads `negate` **after** `negate . sqrt` |
`(negate . length) "hallo"` | `Int` | `-5` |
`negatedRoot = negate . sqrt` | `Floating c => c -> c` | | Constant named `negatedRoot`
## Binary Functions
Expression | Type (:t) | Value | Comment
--- | --- | --- | --------
`div 8 4` | `Integral a => a` | `2` | Similar `mod,gcd,lcm,not`
``8 `div` 4`` | `Integral a => a` | `2` | **Infix** notation using backticks
`8 + 4` | `Num a => a` | `12` | `-,*,^,<, <=, >=, ==, /=`
`(+) 8 4` | `Num a => a` | `12` | **Prefix** notation using parenthesis
`div` | `Integral a => a -> a -> a` | | **Currying** represents multi-argument functions as a chain of single-argument functions |
`div 8` | `Integral a => a -> a` | | binds the **first** argument; Similar `(8/),(8*),(8+),(8-)` |
`div8bySomething = div 8` | `Integral a => a -> a` | |
`div8bySomething 4` | `Integral a => a` | `2` |
`uncurry div` | `Integral c => (c, c) -> c` | | accepts a single tuple argument
`uncurry div (8,4)` | `Integral c => c` | `2` |
`uncurry` | `(a -> b -> c) -> (a, b) -> c`| |
`` (`div` 4) `` | `Integral a => a -> a` | | **Sectioning** binds the **second** argument of infix functions; Similar `(/8),(*8),(+8),(-8)` |
``divSomethingBy4 = (`div` 4)`` | `Integral a => a -> a` | |
`divSomethingBy4 8` | `Integral a => a` | `2` |
`const` | `a -> b -> a` | |
`const 8` | `Num a => b -> a` | | function that ignores the input and always returns 8
`const 8 4` | `Num a => a` | `8` |
## Tuples
Expression | Type (:t) | Value | Comment
--- | --- | --- | --------
`(1, 2)` | `(Num a, Num b) => (a, b)` | `(1, 2)` |
`(1, "hello", True)` | `(Num a => (a, String, Bool)` | `(1, "hello", True)` |
`fst (1, 2)` | `Num a => a` | `1` |
`snd (1, 2)` | `Num b => b` | `2` |
`(,) 1 2` | `Num a => a -> a -> (a, a)` | `(1, 2)` |
## Strings
Expression | Type (:t) | Value | Comment
--- | --- | --- | --------
`"hello" ++ " world"` | `String` | `"hello world"` | String concatenation
`length "hello"` | `Int` | `5` | Length of the string
`null ""` | `Bool` | `True` | Checks if the string is empty
## Control Structures
Expression | Type (:t) | Value | Comment
--- | --- | --- | --------
`if 10/2==5 then "five" else "something else"` | `String` | `"five"` |
`case 1 of { 1 -> "small"; 2 -> "medium"; _ -> "large"}` | `String` | `"small"` | patterns are overlapping
`(let x = 5*2 in (x * 2,x /2))` | `(Fractional b, Num a) => (a, b)` | `(20,5.0)` |
## Lists
Expression | Type (:t) | Value | Comment
--- | --- | --- | --------
`[]` | `[a]` | `[]` |
`1:7:4:[]` | `Num a => [a]` | `[1,7,4]` | actual construction of a list
`[1,7,4]` | `Num a => [a]` | `[1,7,4]` |
`8 : [1,7,4]` | `Num a => [a]` | `[8,1,7,4]` | **cons** operator, short for construct
`[1,7,4]` | `[Num a] => [a]` | `[1,7,4]` |
`(++)` | `[a] -> [a] -> [a]` | |
`[1,7,4] ++ [8]` | `Num a => [a]` | `[1,7,4,8]` |
`[1,7,4] !! 2` | `Num a => [a]` | `4` |
`(!!)` | `[a] -> Int -> a` | |
`head [1,7,4]` | `Num a => a` | `1` | Similar `last`
`tail [1,7,4]` | `[Num a] => [a]` | `[7,4]` | Similar `init`
`length [1,7,4]` | `Int` | `3` |
`null []` | `Bool` | `True` |
`reverse [1,7,4]` | `[Num a] => [a]` | `[4,7,1]` | Similar `Data.List.sort`
`take 2 [1,7,4]` | `[Num a]=>[a]` | `[1,7]` | Similar `drop`
`take` | `Int -> [a] -> [a]` | |
`sum [1,7,4]` | `Num a=>a` | `12` | Similar `product,maximum,minimum,length`
`foldl (-) 0 [1,7,4]` | `Num a => a` | `-12` | `(((0-1)-7)-4)`
`foldl` | `Foldable t => (b -> a -> b) -> b -> t a -> b` | |
`foldr (-) 0 [1,7,4]` | `Num a => a` | `-2` | `(1-(7-(4-0)))`
`elem 7 [1,7,4]` | `Bool` | `True` |
`all even [1,7,4]` | `Bool` | `False` | Similar `any`
`filter even [1,7,4]` | `[Integral a] => [a]` | `[1,7,4]` | Similar `odd,(>1),(/=3)`
`(length . filter (>1) ) [1,7,4]` | `Int` | `2` |
`Data.List.partition (>1) [1,7,4]` | `(Ord a, Num a) => ([a], [a])` | `([7,4],[1])`
`zip [1,7,4] ["one","seven","four","eight"]` | `Num a => [(a, String)]` | `[(1,"one"),(7,"seven"),(4,"four")]` | truncating to the shorter list
`Data.List.sortOn fst $ zip [1,7,4] ["one", "seven", "four"]` | `(Ord b, Num b) => [(b, String)]` | `[(1,"one"),(4,"four"),(7,"seven")]`
`Data.List.sort ["one","seven","four","eight"]` | `[String]` | `["eight","four","one","seven"]`
`reverse $ Data.List.sort ["one","seven","four","eight"]` | `[String]` | `["seven","one","four","eight"]`
`Data.List.sortBy compare ["one","seven","four","eight"]` | `[String]` | `["eight","four","one","seven"]`
`Data.List.sortBy (flip compare) ["one","seven","four","eight"]` | `[String]` | `["seven","one","four","eight"]`
`Data.List.sortBy (curry( (uncurry compare) . (uncurry(Control.Arrow.***) (length,length)))) ["one","seven","four","eight","ten"]` | `[String]` | `["one","ten","four","seven","eight"]` |
`Data.List.sortBy (Data.Ord.comparing length) ["one","seven","four","eight","ten"]` | `[String]` | `["one","ten","four","seven","eight"]`
`map (+1) [1,7,4]` | `Num a => [a]` | `[2,8,5]` |
`map length ["one", "seven", "four"]` | `[Int]` | `[3,5,4]` |
`map (negate . abs) [1,-7,4]` | `Num a => [a]` | `[-1,-7,-4]` |
`map even [1,7,4]` | `[Bool]` | `[False,False,True]` |
`[1..5]` | `(Num a, Enum a) => [a]` | `[1,2,3,4,5]` |
`[1,3..9]` | `(Num a, Enum a) => [a]` | `[1,3,5,7,9]` |
`[1..]` | `(Num a, Enum a) => [a]` | `[1,2,3,4,5,...]` | infinite sequence
`take 4 [10..]` | `(Num a, Enum a) => [a]` | `[1,2,3,4]` | laziness in action
`[x*2 \| x <- [1..5]]` | `[Num a] => [a]` | `[2, 4, 6, 8, 10]` | List comprehension
``[x*2 \| x <- [1..5], x `mod` 3 == 0]`` | `[Num a] => [a]` | `[6]` |
`[ (a,b,c) \| c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2]` | `(Num c, Eq c, Enum c) => [(c, c, c)]` | `[(3,4,5),(6,8,10)]`

View File

@ -1,10 +0,0 @@
# Assignment
You are provided with:
1. A vector of names
2. A vector of ages
Your task is to compute a list containing the UPPERCASE names of the two oldest persons whose names ends with an "a". The result shall be sorted by alphabet.
Complete the corresponding unit test in the file `02-list-operations/list-operations.hs`.

View File

@ -1,23 +0,0 @@
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Avoid reverse" #-}
{-# HLINT ignore "Use void" #-}
import Data.Char (toUpper)
import Data.List (sort, sortOn)
import Test.HUnit
names = ["Oliver", "Emma", "Liam", "Ava", "Noah", "Sophia", "James", "Mia", "Elijah", "Isabella"]
ages = [25, 30, 22, 29, 35, 28, 40, 26, 33, 31]
-- we are looking for the UPPERCASE names of the two oldest persons whose names ends with an a sorted alphabetically"
extractedNames = []
test1 = Test.HUnit.TestCase (assertEqual "ExtractedNames" ["EMMA", "ISABELLA"] extractedNames)
tests = TestList [test1]
main :: IO ()
main = runTestTT test1 >> return ()

View File

@ -1,33 +0,0 @@
-- single parameter
-- two parameters
-- ============= pattern matching =================
-- ============= guards =================
-- ============= recursive algorithms =================
-- ============= recursive list algorithms =================
-- ============= merge sort =================
-- =========== Filter all numbers that can be divided by 10 ===========
-- ========================== standard deviation using lambdas =====================
-- The standard deviation is a measure of the amount of variation or dispersion of a set of values.
-- It is defined as the square root of the average of the squared differences from the mean.
--
-- Formula:
-- σ = sqrt( (1/N) * Σ (xi - μ)^2 )
-- where:
-- - σ is the standard deviation,
-- - N is the number of values,
-- - xi represents each value,
-- - μ is the mean of the values.
stddev seq = 0
-- ======================= order list of strings by length =================

View File

@ -1,13 +0,0 @@
# Assignment - Tower of Hanoi {.alert}
The Tower of Hanoi is a classic mathematical puzzle that involves three rods and a number of disks of different sizes.
The puzzle starts with all the disks stacked in ascending order of size on one rod (the source rod), with the smallest disk on top.
The objective is to move the entire stack to another rod (the destination rod), following these rules:
1. Only one disk can be moved at a time.
2. Each move consists of taking the top disk from one rod and placing it on another rod.
3. No disk may be placed on top of a smaller disk.
**Your task:**
Implement a function that solves the Tower of Hanoi problem for `n` disks. The rods are represented as Chars (e.g. 'a','b','c'). The result shall be a list of moving instructions. Add your implementation to the file `04-hanoi/hanoi.hs` that already contains a test and a helper function that creates the move instruction for a single move.

View File

@ -1,70 +0,0 @@
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use void" #-}
import Test.HUnit
-- some helper function that describes a single move
move :: Int -> Char -> Char -> String
move n src dst = "move disk " ++ show n ++ " from " ++ [src] ++ " to " ++ [dst]
-- transfers a number of disks (the Int) from a source (the first Char) rod to a destination (the second Char) rod via a temp (the third char) rod
-- returns a list of required operations
hanoi :: (Int,Char,Char,Char) -> [String]
--TODO: implement here
-- Source, Destination, Temp
hanoiTests :: [((Int, Char, Char, Char), [String])]
hanoiTests =
[ ( (1, 'a', 'c', 'b'),
[ "move disk 1 from a to c"
]
),
( (1, 'b', 'a', 'c'),
[ "move disk 1 from b to a"
]
),
( (2, 'a', 'c', 'b'),
[ "move disk 1 from a to b",
"move disk 2 from a to c",
"move disk 1 from b to c"
]
),
( (3, 'a', 'c', 'b'),
[ "move disk 1 from a to c",
"move disk 2 from a to b",
"move disk 1 from c to b",
"move disk 3 from a to c",
"move disk 1 from b to a",
"move disk 2 from b to c",
"move disk 1 from a to c"
]
),
( (4, 'a', 'c', 'b'),
[ "move disk 1 from a to b",
"move disk 2 from a to c",
"move disk 1 from b to c",
"move disk 3 from a to b",
"move disk 1 from c to a",
"move disk 2 from c to b",
"move disk 1 from a to b",
"move disk 4 from a to c",
"move disk 1 from b to c",
"move disk 2 from b to a",
"move disk 1 from c to a",
"move disk 3 from b to c",
"move disk 1 from a to b",
"move disk 2 from a to c",
"move disk 1 from b to c"
]
)
]
hanoiTestCases :: [Test]
hanoiTestCases = map (\(input, expected) -> TestCase (assertEqual ("hanoi " ++ show input) expected (hanoi input))) hanoiTests
tests :: Test
tests = TestList hanoiTestCases
main :: IO ()
main = runTestTT tests >> return ()

View File

@ -1,5 +0,0 @@
-- data Entity =
-- data Shape =
-- area :: Shape a -> a

View File

@ -1,72 +0,0 @@
# Complex Numbers
## Assignment
Define a new type `Complex` that allows arithmetic calculations in an intuitive fashion. See below for the rules that shall be implemented. Incrementally make sure that all unit tests in `06-complex/complex.hs` are passed.
## Complex Numbers
Complex numbers are numbers of the form: $z = a + bi$
- **`a`**: The real part of the complex number.
- **`b`**: The imaginary part of the complex number.
- **`i`**: The imaginary unit, where $i^2 = -1$.
Complex numbers are used in mathematics, physics, and engineering to extend the real number system and solve equations that have no real solutions, such as $x^2 + 1 = 0$.
Complex numbers are often represented in both rectangular form $a + bi$ and polar/exponential form $r e^{i\theta}$.
## Rules for Computing with Complex Numbers
### 1. **Addition and Subtraction**
- Add or subtract the real and imaginary parts separately.
- Rule:
$(a + bi) + (c + di) = (a + c) + (b + d)i$
$(a + bi) - (c + di) = (a - c) + (b - d)i$
**Examples**:
1. $(2 + 3i) + (4 + 5i) = 6 + 8i$
2. $(0 + 3i) + (2 + 0i) = 2 + 3i$
3. $(7 + 2i) - (3 + 6i) = 4 - 4i$
4. $(5 + 4i) - (1 + 2i) = 4 + 2i$
### 2. **Multiplication**
- Use the distributive property and the rule $i^2 = -1$.
- Rule:
$(a + bi)(c + di) = (ac - bd) + (ad + bc)i$
**Examples**:
1. $(1 + 2i)(3 + 4i) = -5 + 10i$
2. $(2 + 3i)(1 - i) = 5 + i$
3. $(4 + i)(2 - 3i) = 11 - 10i$
4. $(3 + 2i)(3 + 2i) = 5 + 12i$
### 3. **Division**
- Multiply numerator and denominator by the conjugate of the denominator.
- Rule:
$\frac{a + bi}{c + di} = \frac{(a + bi)(c - di)}{(c + di)(c - di)}$
Simplifies to:
$\frac{(ac + bd) + (bc - ad)i}{c^2 + d^2}$
**Examples**:
1. $\frac{1 + i}{1 - i} = 0 + 1i$
2. $\frac{3 + 2i}{4 + 3i} = \frac{18 + i}{25} = 0.72 - 0.04i$
3. $\frac{2 + i}{1 + i} = \frac{3 - i}{2} = 1.5 - 0.5i$
### 4. **Complex Conjugate**
- The conjugate of $a + bi$ is $a - bi$, used for simplifications and magnitude calculation.
**Examples**:
1. Conjugate of $3 + 4i$ is $3 - 4i$.
2. Conjugate of $5 - i$ is $5 + i$.
3. Conjugate of $-2 + 3i$ is $-2 - 3i$.
### 5. **Magnitude (Modulus)**
- The magnitude is the distance from the origin in the complex plane.
- Rule: $|a + bi| = \sqrt{a^2 + b^2}$
**Examples**:
1. $|3 + 4i| = \sqrt{3^2 + 4^2} = 5$
2. $|1 - i| = \sqrt{1^2 + (-1)^2} = \sqrt{2}$
3. $|0 + 5i| = \sqrt{0^2 + 5^2} = 5$

View File

@ -1,123 +0,0 @@
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use void" #-}
import Test.HUnit
data Complex a = TODO
instance (Show a, Num a, Eq a) => Show (Complex a) where
-- show :: (Show a, Num a, Eq a) => Complex a -> String
-- TODO
instance (Num a, Floating a) => Num (Complex a) where
-- (+) :: (Num a, Floating a) => Complex a -> Complex a -> Complex a
-- (*) :: (Num a, Floating a) => Complex a -> Complex a -> Complex a
-- TODO
-- abs (Complex re im) = TODO
-- signum (Complex re im) = TODO
-- fromInteger n = TODO
-- negate :: (Num a, Floating a) => Complex a -> Complex a
-- negate (Complex re im) = Complex (negate re) (negate im)
instance (Fractional a, Floating a) => Fractional (Complex a) where
-- fromRational r = TODO
-- recip (Complex re im) = TODO
-- (Complex re1 im1) / (Complex re2 im2) = TODO
-- conj :: Num a => Complex a -> Complex a
-- TODO
tests :: Test
tests =
TestList
[ Test.HUnit.TestCase (assertEqual "Show 1+2i" "1+2i" (show $ Complex 1 2)),
Test.HUnit.TestCase (assertEqual "Show 1" "1" (show $ Complex 1 0)),
Test.HUnit.TestCase (assertEqual "Show i" "i" (show i)),
Test.HUnit.TestCase (assertEqual "Show 5i" "5i" (show $ Complex 0 5)),
Test.HUnit.TestCase (assertEqual "Show 0" "0" (show $ Complex 0 0)),
Test.HUnit.TestCase (assertEqual "Compare Equal" True (Complex 2 3 == Complex 2 3)),
Test.HUnit.TestCase (assertEqual "Compare Real Not Equal" False (Complex 1 3 == Complex 2 3)),
Test.HUnit.TestCase
(assertEqual "Compare Imag Not Equal" False (Complex 2 4 == Complex 2 3)),
Test.HUnit.TestCase
(assertEqual "Compare Not Equal" False (Complex 2 3 /= Complex 2 3)),
Test.HUnit.TestCase
( assertEqual
"Addition 1"
(Complex 6.0 8.0)
(Complex 2.0 3.0 + Complex 4.0 5.0)
),
Test.HUnit.TestCase
( assertEqual
"Addition 2"
(Complex 2.0 3.0)
(Complex 0.0 3.0 + Complex 2.0 0.0)
),
Test.HUnit.TestCase
( assertEqual
"Subtraction 1"
(Complex 4.0 (-4.0))
(Complex 7.0 2.0 - Complex 3.0 6.0)
),
Test.HUnit.TestCase
( assertEqual
"Subtraction 2"
(Complex 4.0 2.0)
(Complex 5.0 4.0 - Complex 1.0 2.0)
),
Test.HUnit.TestCase
( assertEqual
"Negation"
(Complex (-7.0) (-2.0))
(negate (Complex 7.0 2.0))
),
Test.HUnit.TestCase
( assertEqual
"Multiplication 1"
(Complex (-5.0) 10.0)
(Complex 1.0 2.0 * Complex 3.0 4.0)
),
Test.HUnit.TestCase
( assertEqual
"Multiplication 2"
(Complex 5.0 1.0)
(Complex 2.0 3.0 * Complex 1.0 (-1.0))
),
Test.HUnit.TestCase
( assertEqual
"Multiplication 3"
(Complex 11.0 (-10.0))
(Complex 4.0 1.0 * Complex 2.0 (-3.0))
),
Test.HUnit.TestCase
( assertEqual
"Multiplication 4"
(Complex 5.0 12.0)
(Complex 3.0 2.0 * Complex 3.0 2.0)
),
Test.HUnit.TestCase
( assertEqual
"Magnitude 1"
(Complex 5.0 0.0)
(abs (Complex 3.0 4.0))
),
Test.HUnit.TestCase
( assertEqual
"Magnitude 2"
(Complex (sqrt 2.0) 0.0)
(abs (Complex 1.0 (-1.0)))
),
Test.HUnit.TestCase
( assertEqual
"Magnitude 3"
(Complex 5.0 0.0)
(abs (Complex 0.0 5.0))
),
TestCase (assertEqual "Conjugate of 3+4i" (Complex 3 (-4)) (conj (Complex 3 4))),
TestCase (assertEqual "Conjugate of 5-i" (Complex 5 1) (conj (Complex 5 (-1)))),
TestCase (assertEqual "Conjugate of -2+3i" (Complex (-2) (-3)) (conj (Complex (-2) 3)))
]
main :: IO ()
main = runTestTT tests >> return ()

View File

@ -1,2 +0,0 @@
import System.IO
import Text.Read (readMaybe)

View File

@ -1,16 +0,0 @@
# Assignment - Statistics
The file `08-statistics/data.csv` contains random data of students. It consists of the following columns:
- first name
- last name
- study subject
The rest of the columns -- and that number may vary -- contain points for different tasks. The first row is a header.
Your task is to Write a program that reads a filename from standard input. The corresponding csv-file shall be parsed and then the following statistics shall be computed and printed to standard output:
- the best student, i.e. the student with the most accumulated points (name and points shall be printed)
- the average points of all rows for each subject (subject and average points shall be printed in ascending order)
Implement your solution in the file `08-statistics/statistics.hs` and upload the file to moodle.

View File

@ -1,97 +0,0 @@
First Name,Last Name,Subject,Task1,Task2,Task3,Task4,Task5
Alice,Johnson,IB,87,45,68,92,33
Bob,Smith,CSB,56,79,42,88,12
Charlie,Williams,IMB,73,94,35,64,89
David,Brown,UIB,41,59,77,29,83
Emma,Jones,IB,99,21,48,75,56
Frank,Garcia,CSB,32,81,94,55,23
Grace,Miller,IMB,74,53,61,95,39
Henry,Davis,UIB,90,40,28,76,65
Isabella,Rodriguez,IB,57,86,72,49,100
Jack,Martinez,CSB,64,31,59,97,14
Liam,Hernandez,IMB,85,67,50,78,41
Mia,Lopez,UIB,44,92,33,56,81
Noah,Gonzalez,IB,68,39,74,82,47
Olivia,Wilson,CSB,95,20,62,53,34
Paul,Anderson,IMB,29,88,49,31,77
Sophia,Thomas,UIB,81,56,93,42,69
Ethan,Moore,IB,47,77,65,59,92
Zoe,Taylor,CSB,38,95,40,70,58
Lucas,Harris,IMB,99,45,83,62,37
Ava,Clark,UIB,53,66,91,85,29
Benjamin,Walker,IB,71,58,80,34,61
Chloe,Hall,CSB,76,23,67,47,94
Daniel,Allen,IMB,39,82,55,90,73
Ella,Young,UIB,86,32,99,60,25
James,King,IB,42,75,48,93,57
Madison,Wright,CSB,63,54,72,51,88
Nathan,Scott,IMB,91,37,79,68,45
Emma,Green,UIB,50,89,31,77,61
Jacob,Baker,IB,74,69,58,46,97
Emily,Adams,CSB,88,41,66,79,30
Samuel,Nelson,IMB,33,85,92,40,50
Sophia,Carter,UIB,69,47,71,58,94
Alexander,Mitchell,IB,57,98,36,61,82
Victoria,Perez,CSB,92,28,64,73,49
William,Roberts,IMB,45,76,89,55,31
Ella,Turner,UIB,78,59,41,96,68
Daniel,Phillips,IB,60,32,85,71,43
Natalie,Campbell,CSB,55,74,99,28,79
Mason,Parker,IMB,93,50,39,82,66
Hannah,Evans,UIB,48,69,57,95,22
Christopher,Edwards,IB,90,88,34,53,72
Lucy,Collins,CSB,31,46,81,77,56
Gabriel,Stewart,IMB,65,79,62,90,44
Avery,Morris,UIB,80,23,47,36,97
Ryan,Ross,IB,58,71,83,45,74
Sophie,Reed,CSB,99,33,60,81,51
Isaac,Adams,IMB,42,92,30,57,85
Leah,Bennett,UIB,76,64,55,89,39
Tyler,Barnes,IB,67,40,78,95,20
Scarlett,Morgan,CSB,34,84,91,47,53
John,White,IMB,89,25,72,69,98
Eleanor,James,UIB,54,58,49,87,62
Caleb,Wood,IB,31,100,37,46,81
Amelia,Hill,CSB,73,45,79,66,50
Julian,Scott,IMB,97,34,89,22,76
Charlotte,Bryant,UIB,63,92,41,88,32
Isaiah,Mitchell,IB,81,68,52,39,74
Lily,Foster,CSB,56,97,44,85,29
Elijah,Howard,IMB,70,61,36,90,99
Peyton,Washington,UIB,49,82,67,31,75
Xavier,Cook,IB,88,55,93,42,64
Nora,Bailey,CSB,37,72,57,96,47
Owen,Rivera,IMB,59,30,80,77,91
Harper,Cooper,UIB,50,99,33,28,65
Eli,Fisher,IB,78,40,85,92,53
Mila,Harrison,CSB,62,47,71,89,31
Adam,Romero,IMB,34,66,98,42,75
Stella,Patel,UIB,96,39,54,87,68
Carson,Cox,IB,42,55,79,100,27
Luna,Sanders,CSB,88,74,46,29,83
Connor,Henderson,IMB,97,32,53,60,98
Brooklyn,Bishop,UIB,45,90,61,72,31
Andrew,Griffin,IB,58,42,87,65,49
Natalia,Russell,CSB,70,23,74,56,92
Jason,Myers,IMB,81,67,35,48,79
Alexa,Hayes,UIB,37,98,55,93,42
Christian,Long,IB,64,71,82,45,57
Zoey,Gibson,CSB,90,54,38,100,73
Brayden,Graham,IMB,39,89,66,53,31
Maya,Sullivan,UIB,85,77,62,94,25
Jaxon,Ford,IB,48,35,100,71,83
Claire,Wallace,CSB,59,46,88,38,76
Dominic,Cole,IMB,78,92,44,67,50
Eva,Jenkins,UIB,100,29,53,79,61
Riley,Perkins,IB,55,97,31,84,30
Leo,Brooks,CSB,72,40,91,45,99
Hailey,Reynolds,IMB,31,58,77,90,28
Aaron,Fox,UIB,83,72,61,35,48
Kennedy,Stevens,IB,41,66,85,92,53
Sebastian,Webb,CSB,99,33,60,81,51
Melanie,Ward,IMB,76,64,55,89,39
Adrian,Bishop,UIB,67,40,78,95,20
Jasmine,Cook,IB,34,84,91,47,53
Miles,Barnes,CSB,89,25,72,69,98
Savannah,Wood,IMB,54,58,49,87,62
Hudson,Hill,UIB,31,100,37,46,81
1 First Name Last Name Subject Task1 Task2 Task3 Task4 Task5
2 Alice Johnson IB 87 45 68 92 33
3 Bob Smith CSB 56 79 42 88 12
4 Charlie Williams IMB 73 94 35 64 89
5 David Brown UIB 41 59 77 29 83
6 Emma Jones IB 99 21 48 75 56
7 Frank Garcia CSB 32 81 94 55 23
8 Grace Miller IMB 74 53 61 95 39
9 Henry Davis UIB 90 40 28 76 65
10 Isabella Rodriguez IB 57 86 72 49 100
11 Jack Martinez CSB 64 31 59 97 14
12 Liam Hernandez IMB 85 67 50 78 41
13 Mia Lopez UIB 44 92 33 56 81
14 Noah Gonzalez IB 68 39 74 82 47
15 Olivia Wilson CSB 95 20 62 53 34
16 Paul Anderson IMB 29 88 49 31 77
17 Sophia Thomas UIB 81 56 93 42 69
18 Ethan Moore IB 47 77 65 59 92
19 Zoe Taylor CSB 38 95 40 70 58
20 Lucas Harris IMB 99 45 83 62 37
21 Ava Clark UIB 53 66 91 85 29
22 Benjamin Walker IB 71 58 80 34 61
23 Chloe Hall CSB 76 23 67 47 94
24 Daniel Allen IMB 39 82 55 90 73
25 Ella Young UIB 86 32 99 60 25
26 James King IB 42 75 48 93 57
27 Madison Wright CSB 63 54 72 51 88
28 Nathan Scott IMB 91 37 79 68 45
29 Emma Green UIB 50 89 31 77 61
30 Jacob Baker IB 74 69 58 46 97
31 Emily Adams CSB 88 41 66 79 30
32 Samuel Nelson IMB 33 85 92 40 50
33 Sophia Carter UIB 69 47 71 58 94
34 Alexander Mitchell IB 57 98 36 61 82
35 Victoria Perez CSB 92 28 64 73 49
36 William Roberts IMB 45 76 89 55 31
37 Ella Turner UIB 78 59 41 96 68
38 Daniel Phillips IB 60 32 85 71 43
39 Natalie Campbell CSB 55 74 99 28 79
40 Mason Parker IMB 93 50 39 82 66
41 Hannah Evans UIB 48 69 57 95 22
42 Christopher Edwards IB 90 88 34 53 72
43 Lucy Collins CSB 31 46 81 77 56
44 Gabriel Stewart IMB 65 79 62 90 44
45 Avery Morris UIB 80 23 47 36 97
46 Ryan Ross IB 58 71 83 45 74
47 Sophie Reed CSB 99 33 60 81 51
48 Isaac Adams IMB 42 92 30 57 85
49 Leah Bennett UIB 76 64 55 89 39
50 Tyler Barnes IB 67 40 78 95 20
51 Scarlett Morgan CSB 34 84 91 47 53
52 John White IMB 89 25 72 69 98
53 Eleanor James UIB 54 58 49 87 62
54 Caleb Wood IB 31 100 37 46 81
55 Amelia Hill CSB 73 45 79 66 50
56 Julian Scott IMB 97 34 89 22 76
57 Charlotte Bryant UIB 63 92 41 88 32
58 Isaiah Mitchell IB 81 68 52 39 74
59 Lily Foster CSB 56 97 44 85 29
60 Elijah Howard IMB 70 61 36 90 99
61 Peyton Washington UIB 49 82 67 31 75
62 Xavier Cook IB 88 55 93 42 64
63 Nora Bailey CSB 37 72 57 96 47
64 Owen Rivera IMB 59 30 80 77 91
65 Harper Cooper UIB 50 99 33 28 65
66 Eli Fisher IB 78 40 85 92 53
67 Mila Harrison CSB 62 47 71 89 31
68 Adam Romero IMB 34 66 98 42 75
69 Stella Patel UIB 96 39 54 87 68
70 Carson Cox IB 42 55 79 100 27
71 Luna Sanders CSB 88 74 46 29 83
72 Connor Henderson IMB 97 32 53 60 98
73 Brooklyn Bishop UIB 45 90 61 72 31
74 Andrew Griffin IB 58 42 87 65 49
75 Natalia Russell CSB 70 23 74 56 92
76 Jason Myers IMB 81 67 35 48 79
77 Alexa Hayes UIB 37 98 55 93 42
78 Christian Long IB 64 71 82 45 57
79 Zoey Gibson CSB 90 54 38 100 73
80 Brayden Graham IMB 39 89 66 53 31
81 Maya Sullivan UIB 85 77 62 94 25
82 Jaxon Ford IB 48 35 100 71 83
83 Claire Wallace CSB 59 46 88 38 76
84 Dominic Cole IMB 78 92 44 67 50
85 Eva Jenkins UIB 100 29 53 79 61
86 Riley Perkins IB 55 97 31 84 30
87 Leo Brooks CSB 72 40 91 45 99
88 Hailey Reynolds IMB 31 58 77 90 28
89 Aaron Fox UIB 83 72 61 35 48
90 Kennedy Stevens IB 41 66 85 92 53
91 Sebastian Webb CSB 99 33 60 81 51
92 Melanie Ward IMB 76 64 55 89 39
93 Adrian Bishop UIB 67 40 78 95 20
94 Jasmine Cook IB 34 84 91 47 53
95 Miles Barnes CSB 89 25 72 69 98
96 Savannah Wood IMB 54 58 49 87 62
97 Hudson Hill UIB 31 100 37 46 81

View File

@ -1,17 +0,0 @@
import Data.List (nub, sortOn)
import System.IO
-- separator -> Input -> Separated Input
-- split :: Char -> String -> [String]
-- data Entry =
-- averages :: [Entry] -> [(String, Float)]
-- content -> Entries
-- parse :: String -> [Entry]
main :: IO ()
main = do
putStrLn "Enter a filename: "
putStrLn "..."