diff --git a/concepts/functions/.meta/config.json b/concepts/functions/.meta/config.json index 179d146ca..10163bf75 100644 --- a/concepts/functions/.meta/config.json +++ b/concepts/functions/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb", - "authors": ["ErikSchierboom"], + "blurb": "Functions split a program up into logical units of computation. They optionally take arguments and return values.", + "authors": ["bobtfish"], "contributors": [] } diff --git a/concepts/functions/about.md b/concepts/functions/about.md index 1761fddf2..842edbb99 100644 --- a/concepts/functions/about.md +++ b/concepts/functions/about.md @@ -1,40 +1,92 @@ # About -Functions in Go are considered [first class citizens][first-class-functions] making them very powerful. They can be used as: +A function allows you to group code into a reusable unit. +It consists of the `func` keyword, the name of the function, and a comma-separated list of zero or more parameters and types in round brackets. +All parameters must be explicitly typed, there is no type inference for parameters. +There are no default values for parameters, all function parameters are required. -- [public and private][public-vs-private] functions of a package -- attached to custom types also called a [method][methods] ([public and private][public-vs-private]) -- assigned to variables -- passed into to functions as parameters and returned from functions (higher order functions) -- create custom types -- be used anonymously -- be used as closures +```go +import "fmt" + +// No parameters +func PrintHello() { + fmt.Println("Hello") +} + +// One parameter +func PrintHelloName(name string) { + fmt.Println("Hello " + name) +} +``` -A function can have zero or more parameters. All parameters must be explicitly typed, there is no type inference for parameters. A function can also have multiple return values which must also be explicitly typed. Values are returned from functions using the [`return` keyword][return]. To allow a function, or a method to be [called by code in other packages][public-vs-private], the name of the function must start with a capital letter. +## Return Values + +The function arguments are followed by zero or more return values which must also be explicitly typed. +Single return values are left bare, multiple return values are wrapped in parenthesis. +Values are returned to the calling code from functions using the [`return` keyword][return]. +There can be multiple `return` statements in a function. +The execution of the function ends as soon as it hits one of those `return`s. +If multiple values are to be returned from a function, they are comma seperated. +More information about idiomatic use of [multiple return values][concept-multiple-return-values] can be found in the linked concept. ```go -package greeting +func Hello(name string) string { + return "Hello " + name +} -// Hello is a public function +func HelloAndGoodbye(name string) (string, string) { + return "Hello " + name, "Goodbye " + name +} +``` + +## Invoking Functions + +Invoking a function is done by specifying the function name and passing arguments for each of the function's parameters in parenthesis. + +```go +import "fmt" + +// No parameters, no return value +func PrintHello() { + fmt.Println("Hello") +} +// Called like this: +PrintHello() + +// One parameter, one return value func Hello(name string) string { - return hello(name) + return "Hello " + name } +// Called like this: +greeting := Hello("Dave") -// hello is a private function -func hello(name string) string { - return "Hello " + name +// Multiple parameters, multiple return values +func SumAndMultiply(a, b int) (int, int) { + return a+b, a*b } +// Called like this: +aplusb, atimesb := SumAndMultiply(a, b) ``` -Invoking a function from inside the same package is done by specifying the function name and passing arguments for each of the function's parameters. +## Named Return Values and Naked Return -Invoking a function from another package is done by specifying the package name as well as the function name. +As well as parameters, return values can optionally be named. +If named return values are used, a `return` statement without arguments will return those values, this is known as a 'naked' return. ```go -phrase := greeting.Hello("John") +func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) { + sum, mult = a+b, a*b + sum -= c + mult -= c + return +} ``` +## Other types of functions + +Functions in Go are considered [first class citizens][first-class-functions] making them very powerful. +There are a number of other concepts around functions like [concept:go/methods]() and anonymous functions which you will meet later in your journey. + [first-class-functinos]: https://golangbot.com/first-class-functions -[methods]: https://golang.org/ref/spec#Method_declarations [return]: https://golang.org/ref/spec#Return_statements -[public-vs-private]: https://golang.org/ref/spec#Exported_identifiers +[concept-multiple-return-values]: /tracks/go/concepts/multiple-return-values diff --git a/concepts/functions/introduction.md b/concepts/functions/introduction.md index b6ec4e0ac..538ed0d14 100644 --- a/concepts/functions/introduction.md +++ b/concepts/functions/introduction.md @@ -1,46 +1,83 @@ # Introduction -TODO: the content below is copied from the exercise introduction and probably needs rewriting to a proper concept introduction +A function allows you to group code into a reusable unit. +It consists of the `func` keyword, the name of the function, and a comma-separated list of zero or more parameters and types in round brackets. +All parameters must be explicitly typed, there is no type inference for parameters. +There are no default values for parameters, all function parameters are required. -## packages - -## functions - -## variables +```go +import "fmt" -Go is a statically-typed language, which means that everything has a type at compile-time. Assigning a value to a name is referred to as defining a variable. A variable can be defined either by explicitly specifying its type, or by assigning a value to have the Go compiler infer its type based on the assigned value. +// No parameters +func PrintHello() { + fmt.Println("Hello") +} -```go -var explicit int // Explicitly typed -implicit := 10 // Implicitly typed +// One parameter +func PrintHelloName(name string) { + fmt.Println("Hello " + name) +} ``` -The value of a variable can be assigned using the `:=` and updated using the `=`. Once defined, a variable's type can never change. +## Return Values + +The function arguments are followed by zero or more return values which must also be explicitly typed. +Single return values are left bare, multiple return values are wrapped in parenthesis. +Values are returned to the calling code from functions using the `return` keyword. +There can be multiple `return` statements in a function. +The execution of the function ends as soon as it hits one of those `return`s. +If multiple values are to be returned from a function, they are comma seperated. ```go -count := 1 // Assign initial value -count = 2 // Update to new value +func Hello(name string) string { + return "Hello " + name +} -// Compiler error when assigning different type -// count = false +func HelloAndGoodbye(name string) (string, string) { + return "Hello " + name, "Goodbye " + name +} ``` -A function can have zero or more parameters. All parameters must be explicitly typed, there is no type inference for parameters. A function can also have multiple return values which must also be explicitly typed. Values are returned from functions using the `return` keyword. To allow a function to be called by code in other packages, the name of the function must start with a capital letter. +## Invoking Functions + +Invoking a function is done by specifying the function name and passing arguments for each of the function's parameters in parenthesis. ```go -package greeting +import "fmt" -// Hello is a public function +// No parameters, no return value +func PrintHello() { + fmt.Println("Hello") +} +// Called like this: +PrintHello() + +// One parameter, one return value func Hello(name string) string { - return hello(name) + return "Hello " + name } +// Called like this: +greeting := Hello("Dave") -// hello is a private function -func hello(name string) string { - return "Hello " + name +// Multiple parameters, multiple return values +func SumAndMultiply(a, b int) (int, int) { + return a+b, a*b } +// Called like this: +aplusb, atimesb := SumAndMultiply(a, b) ``` -Invoking a function is done by specifying the function name and passing arguments for each of the function's parameters. +## Named Return Values and Naked Return + +As well as parameters, return values can optionally be named. +If named return values are used, a `return` statement without arguments will return those values, this is known as a 'naked' return. + +```go +func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) { + sum, mult = a+b, a*b + sum -= c + mult -= c + return +} +``` -Go supports two types of comments. Single line comments are preceded by `//` and multiline comments are inserted between `/*` and `*/`. diff --git a/concepts/functions/links.json b/concepts/functions/links.json index 1f106196c..ffec70ede 100644 --- a/concepts/functions/links.json +++ b/concepts/functions/links.json @@ -1,24 +1,4 @@ [ - { - "url": "https://golang.org/ref/spec#Assignments", - "description": "assignment" - }, - { - "url": "https://golang.org/ref/spec#Assignments", - "description": "assignment" - }, - { - "url": "https://golang.org/ref/spec#Operators", - "description": "operators" - }, - { - "url": "https://golang.org/ref/spec#Exported_identifiers", - "description": "public-vs-private" - }, - { - "url": "https://golang.org/ref/spec#Method_declarations", - "description": "methods" - }, { "url": "https://golang.org/ref/spec#Exported_identifiers", "description": "public-vs-private" @@ -28,8 +8,11 @@ "description": "return" }, { - "url": "https://golang.org/ref/spec#Exported_identifiers", - "description": "public-vs-private" + "url": "https://tour.golang.org/basics/4", + "description": "A tour of Go: functions" }, - { "url": "https://golang.org/ref/spec#Comments", "description": "comments" } + { + "url": "https://gobyexample.com/functions", + "description": "Go by Example: Functions" + } ] diff --git a/config.json b/config.json index 079ee4ded..db5778a44 100644 --- a/config.json +++ b/config.json @@ -154,6 +154,22 @@ ], "status": "beta" }, + { + "name": "Lasagna Master", + "slug": "lasagna-master", + "uuid": "41167bbf-c2e6-4946-83d8-0ea108e5ce8d", + "concepts": [ + "functions" + ], + "prerequisites": [ + "conditionals-if", + "numbers", + "strings", + "comparison", + "slices" + ], + "status": "beta" + }, { "name": "Need For Speed", "slug": "need-for-speed", @@ -1851,6 +1867,11 @@ "slug": "interfaces", "uuid": "a492060c-11e7-448e-bdbd-6a907cec9493" }, + { + "name": "Functions", + "slug": "functions", + "uuid": "e737f47b-5d1a-423a-8d6a-cd201a6e4e44" + }, { "name": "Iteration", "slug": "iteration", diff --git a/exercises/concept/lasagna-master/.docs/hints.md b/exercises/concept/lasagna-master/.docs/hints.md new file mode 100644 index 000000000..875712784 --- /dev/null +++ b/exercises/concept/lasagna-master/.docs/hints.md @@ -0,0 +1,24 @@ +# Hints + +## 1. Estimate the preparation time + +- Use the `len()` keyword to determine the number of layers (length of the layers array). + +## 2. Compute the amounts of noodles and sauce needed + +- First, set up two variables to track the amount of noodles and sauce. +- Use a for loop to iterate through the layers. +- If you encounter a `'noodles'` or `'sauce'` layer in your loop, increase the amount stored in the respective variable accordingly. + +## 3. Add the secret ingredient + +- Revisit [slices][concept-slices] to find out how to retrieve an element from an slice and how to add something the end of a slice. +- The index of the last element in an array `a` is `len(a) - 1`. + +## 4. Scale the recipe + +- First make a new slice of the same size as the input slice +- Use a [for range loop][concept-range-iteration] to iterate through the input slice and generate the output slice + +[concept-conditonals-if]: /tracks/go/concepts/conditionals-if +[concept-slices]: /tracks/go/concepts/slices diff --git a/exercises/concept/lasagna-master/.docs/instructions.md b/exercises/concept/lasagna-master/.docs/instructions.md new file mode 100644 index 000000000..ea0d53187 --- /dev/null +++ b/exercises/concept/lasagna-master/.docs/instructions.md @@ -0,0 +1,80 @@ +# Instructions + +In this exercise you are going to write some more code related to preparing and cooking your brilliant lasagna from your favorite cookbook. + +You have four tasks. +The first one is related to the cooking itself, the other three are about the perfect preparation. + +## 1. Estimate the preparation time + +For the next lasagna that you will prepare, you want to make sure you have enough time reserved so you can enjoy the cooking. +You already made a plan which layers your lasagna will have. +Now you want to estimate how long the preparation will take based on that. + +Implement a function `preparationTime` that accepts an array of layers as a `[]string` and the average preparation time per layer in minutes as an `int`. +The function should return the estimate for the total preparation time based on the number of layers as an `int`. +Go has no default values for functions. +If the average preperation time is passed as `0` (the default initial value for an `int`), then the default value of `2` should be used. + +```go +layers := []string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"} +preparationTime(layers, 3) +// => 18 +preparationTime(layers, 0) +// => 12 +``` + +## 2. Compute the amounts of noodles and sauce needed + +Besides reserving the time, you also want to make sure you have enough sauce and noodles to cook the lasagna of your dreams. +For each noodle layer in your lasagna, you will need 50 grams of noodles. +For each sauce layer in your lasagna, you will need 0.2 liters of sauce. + +Define the function `quantities` that takes an array of layers as parameter as a `[]string`. +The function will then determine the quantity of noodles and sauce needed to make your meal. +The result should be returned as two values of `noodles` as an `int` and `sauce` as a `float64`. + +```go +quantities([]string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"}) +// => 100, 0.4 +``` + +## 3. Add the secret ingredient + +A while ago you visited a friend and ate lasagna there. +It was amazing and had something special to it. +The friend sent you the list of ingredients and told you the last item on the list is the "secret ingredient" that made the meal so special. +Now you want to add that secret ingredient to your recipe as well. + +Write a function `addSecretIngredient` that accepts two arrays of ingredients of type `[]string` as parameters. +The first parameter is the list your friend sent you, the second is the ingredient list for your own recipe. +The function should generate a new slice and add the last item from your friends list to the end of your list. +Neither argument should not be modified. + +```go +friendsList := []string{"noodles", "sauce", "mozzarella", "kampot pepper"} +myList := []string{"noodles", "meat", "sauce", "mozzarella"} + +addSecretIngredient(friendsList, myList) +// => []string{"noodles", "meat", "sauce", "mozzarella", "kampot pepper"} +``` + +## 4. Scale the recipe + +The amounts listed in your cookbook only yield enough lasagna for two portions. +Since you want to cook for more people next time, you want to calculate the amounts for different numbers of portions. + +Implement a function `ScaleRecipe` that takes two parameters. + +- A slice of float64 amounts needed for 2 portions. +- The number of portions you want to cook. + +The function should return a slice of float64s with the amounts needed for the desired number of portions. +You want to keep the original recipe though. +This means, in this task the amounts argument should not be modified. + +```go +quantities := []float64{ 1.2, 3.6, 10.5 } +scaledQuantities := ScaleRecipe(quantities, 4) +// => []float64{ 2.4, 7.2, 21 } +``` diff --git a/exercises/concept/lasagna-master/.docs/introduction.md b/exercises/concept/lasagna-master/.docs/introduction.md new file mode 100644 index 000000000..aa263e6e2 --- /dev/null +++ b/exercises/concept/lasagna-master/.docs/introduction.md @@ -0,0 +1,123 @@ +# Introduction + +A function allows you to group code into a reusable unit. +It consists of the `func` keyword, the name of the function, and a comma-separated list of zero or more parameters and types in round brackets. +All parameters must be explicitly typed, there is no type inference for parameters. +There are no default values for parameters, all function parameters are required. + +```go +import "fmt" + +// No parameters +func PrintHello() { + fmt.Println("Hello") +} + +// Two parameters +func PrintGreetingName(greeting string, name string) { + fmt.Println(greeting + " " + name) +} +``` + +Parameters of the same type can be declared together, followed by a single type declaration. + +```go +import "fmt" + +func PrintGreetingName(greeting, name string) { + fmt.Println(greeting + " " + name) +} +``` + +## Return Values + +The function arguments are followed by zero or more return values which must also be explicitly typed. +Single return values are left bare, multiple return values are wrapped in parenthesis. +Values are returned to the calling code from functions using the `return` keyword. +There can be multiple `return` statements in a function. +The execution of the function ends as soon as it hits one of those `return`s. +If multiple values are to be returned from a function, they are comma seperated. + +```go +func Hello(name string) string { + return "Hello " + name +} + +func HelloAndGoodbye(name string) (string, string) { + return "Hello " + name, "Goodbye " + name +} +``` + +## Invoking Functions + +Invoking a function is done by specifying the function name and passing arguments for each of the function's parameters in parenthesis. + +```go +import "fmt" + +// No parameters, no return value +func PrintHello() { + fmt.Println("Hello") +} +// Called like this: +PrintHello() + +// One parameter, one return value +func Hello(name string) string { + return "Hello " + name +} +// Called like this: +greeting := Hello("Dave") + +// Multiple parameters, multiple return values +func SumAndMultiply(a, b int) (int, int) { + return a+b, a*b +} +// Called like this: +aplusb, atimesb := SumAndMultiply(a, b) +``` + +## Named Return Values and Naked Return + +As well as parameters, return values can optionally be named. +If named return values are used, a `return` statement without arguments will return those values, this is known as a 'naked' return. + +```go +func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) { + sum, mult = a+b, a*b + sum -= c + mult -= c + return +} +``` + +## Pointers + +Functions in go pass their arguments by value. +This means that the arguments are copied, and any changes to those arguments within the function will not be seen outside the function. + +```go +val := 2 +func MultiplyByTwo(v int) int { + v = v * 2 + return v +} +newval := MultiplyByTwo(val) +// newval is 4, val is 2 +``` + +To affect the values a function is called with, it is possible to pass pointer arguments, using the `*` syntax in the function, and the `&` syntax to generate a pointer. + +```go +func swap(x, y *int) { + var temp int + temp = *x + *x = *y + *y = temp +} + +var a int = 100 +var b int = 200 +swap(&a, &b) +// a = 200, b = 100 +``` diff --git a/exercises/concept/lasagna-master/.meta/config.json b/exercises/concept/lasagna-master/.meta/config.json new file mode 100644 index 000000000..3709a56e5 --- /dev/null +++ b/exercises/concept/lasagna-master/.meta/config.json @@ -0,0 +1,11 @@ +{ + "blurb": "Dive deeper into Go functions while preparing to cook the perfect lasagna.", + "authors": ["bobtfish"], + "contributors": [], + "files": { + "solution": ["lasagna-master.go"], + "test": ["lasagna-master_test.go"], + "exemplar": [".meta/exemplar.go"] + }, + "forked_from": ["javascript/lasagna-master"] +} diff --git a/exercises/concept/lasagna-master/.meta/design.md b/exercises/concept/lasagna-master/.meta/design.md new file mode 100644 index 000000000..14b9852f1 --- /dev/null +++ b/exercises/concept/lasagna-master/.meta/design.md @@ -0,0 +1,42 @@ +# Design + +## Learning objectives + +- How to define a function +- How to invoke a function +- How to pass parameters to a function +- How to recieve returned values from a function + +## Out of Scope + +The following topics will be introduced later and should therefore not be part of this concept exercise. + +- Public vs Private functions +- Pointers +- Variadic functions +- Methods +- Anonymous functions +- Recursion +- Closures +- Higher Order Functions + +## Concepts + +The Concept this exercise unlocks is: + +- `functions` + +## Prerequisites + +- `strings` are needed in the exercise +- `numbers` are needed in the exercise +- `comparison` is needed in the exercise +- `conditionals-if` is needed in the exercise +- `slices` are needed in the exercise + +## Notes + +The story was inspired by the [Lasagna Master Exercise in the Javascipt track][javascript-lasagna-master]. +Most tasks needed to be changed though to achieve an exercise that fits the Go learning objectives. + +[javascript-lasagna-master]: https://github.com/exercism/javascript/blob/main/exercises/concept/lasagna-master/.docs/instructions.md diff --git a/exercises/concept/lasagna-master/.meta/exemplar.go b/exercises/concept/lasagna-master/.meta/exemplar.go new file mode 100644 index 000000000..2b4e58f10 --- /dev/null +++ b/exercises/concept/lasagna-master/.meta/exemplar.go @@ -0,0 +1,39 @@ +package lasagna + +// PreparationTim estimates the preparation time based on the number of layers and an average time per layer and returns it.. +func PreparationTime(layers []string, avgPrepTime int) int { + if avgPrepTime == 0 { + avgPrepTime = 2 + } + return len(layers) * avgPrepTime +} + +// Quantities calculates and returns how many noodles and much sauce are needed for the given layers. +func Quantities(layers []string) (noodles int, sauce float64) { + numLayers := len(layers) + for i := 0; i < numLayers; i++ { + if layers[i] == "noodles" { + noodles += 50 + } + + if layers[i] == "sauce" { + sauce += 0.2 + } + } + return +} + +// AddSecretIngredient adds the secret ingredient from the ingredient list that a friend provided to your ingredient list and returns your new ingredient list. +func AddSecretIngredient(friendsList, myList []string) []string { + lastIndex := len(friendsList) - 1 + return append(myList, friendsList[lastIndex]) +} + +// ScaleRecipe makes a new slice of float64s from an input slice scaled by a number of portions. +func ScaleRecipe(list []float64, portions int) []float64 { + output := make([]float64, len(list)) + for i := 0; i < len(list); i++ { + output[i] = list[i] * float64(portions) / 2 + } + return output +} diff --git a/exercises/concept/lasagna-master/go.mod b/exercises/concept/lasagna-master/go.mod new file mode 100644 index 000000000..d53312e95 --- /dev/null +++ b/exercises/concept/lasagna-master/go.mod @@ -0,0 +1,3 @@ +module lasagna + +go 1.14 diff --git a/exercises/concept/lasagna-master/lasagna-master.go b/exercises/concept/lasagna-master/lasagna-master.go new file mode 100644 index 000000000..5d32bb7ce --- /dev/null +++ b/exercises/concept/lasagna-master/lasagna-master.go @@ -0,0 +1,9 @@ +package lasagna + +// TODO: define the 'PreparationTime()' function + +// TODO: define the 'Quantities()' function + +// TODO: define the 'AddSecretIngredient()' function + +// TODO: define the 'ScaleRecipe()' function diff --git a/exercises/concept/lasagna-master/lasagna-master_test.go b/exercises/concept/lasagna-master/lasagna-master_test.go new file mode 100644 index 000000000..20d6edda6 --- /dev/null +++ b/exercises/concept/lasagna-master/lasagna-master_test.go @@ -0,0 +1,222 @@ +package lasagna + +import ( + "math" + "reflect" + "testing" +) + +type preparationTimeTests struct { + name string + layers []string + time, expected int +} + +func TestPreparationTime(t *testing.T) { + tests := []preparationTimeTests{ + { + name: "Preparation time for many layers with custom average time", + layers: []string{ + "sauce", + "noodles", + "béchamel", + "meat", + "mozzarella", + "noodles", + "ricotta", + "eggplant", + "béchamel", + "noodles", + "sauce", + "mozzarella", + }, + time: 1, + expected: 12, + }, + { + name: "Preparation time for few layers", + layers: []string{ + "sauce", + "noodles", + }, + time: 3, + expected: 6, + }, + { + name: "Preparation time for default case", + layers: []string{ + "sauce", + "noodles", + }, + time: 0, + expected: 4, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := PreparationTime(tt.layers, tt.time); got != tt.expected { + t.Errorf("PreparationTime(%v, %d) = %d; want %d", tt.layers, tt.time, got, tt.expected) + } + }) + + } +} + +type quantitiesTest struct { + name string + layers []string + expNoodles int + expSauce float64 +} + +func TestQuantities(t *testing.T) { + tests := []quantitiesTest{ + quantitiesTest{ + name: "few layers", + layers: []string{"noodles", "sauce", "noodles"}, + expNoodles: 100, + expSauce: 0.2, + }, + quantitiesTest{ + name: "many layers", + layers: []string{ + "sauce", + "noodles", + "béchamel", + "meat", + "mozzarella", + "noodles", + "ricotta", + "eggplant", + "béchamel", + "noodles", + "sauce", + "mozzarella"}, + expNoodles: 150, + expSauce: 0.4, + }, + quantitiesTest{ + name: "no noodles", + layers: []string{ + "sauce", + "meat", + "mozzarella", + "sauce", + "mozzarella"}, + expNoodles: 0, + expSauce: 0.4, + }, + quantitiesTest{ + name: "no sauce", + layers: []string{ + "noodles", + "meat", + "mozzarella", + "noodles", + "mozzarella"}, + expNoodles: 100, + expSauce: 0.0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotNoodles, gotSauce := Quantities(tt.layers) + if gotNoodles != tt.expNoodles { + t.Errorf("quantities(%v) = %d noodles; want %d", tt.layers, tt.expNoodles, gotNoodles) + } + if gotSauce != tt.expSauce { + t.Errorf("quantities(%v) = %f sauce; want %f", tt.layers, tt.expSauce, gotSauce) + } + }) + } +} + +type secretTest struct { + name string + friendsList []string + myList []string + expected []string +} + +func TestAddSecretIngredient(t *testing.T) { + tests := []secretTest{ + secretTest{ + name: "Adds secret ingredient", + friendsList: []string{"sauce", "noodles", "béchamel", "marjoram"}, + myList: []string{"sauce", "noodles", "meat", "tomatoes"}, + expected: []string{"sauce", "noodles", "meat", "tomatoes", "marjoram"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + friendsList := make([]string, len(tt.friendsList)) + copy(friendsList, tt.friendsList) + myList := make([]string, len(tt.myList)) + copy(myList, tt.myList) + got := AddSecretIngredient(tt.friendsList, tt.myList) + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("addSecretIngredient(%v, %v) = %v want %v", tt.friendsList, tt.myList, got, tt.expected) + } + if !reflect.DeepEqual(friendsList, tt.friendsList) { + t.Errorf("addSecretIngredient permuted friendsList (was %v, now %v), should not alter inputs", tt.friendsList, friendsList) + } + if !reflect.DeepEqual(myList, tt.myList) { + t.Errorf("addSecretIngredient permuted myList (was %v, now %v), should not alter inputs", tt.myList, myList) + } + }) + } +} + +type scaleRecipeTest struct { + name string + input []float64 + portions int + expected []float64 +} + +func TestSscaleRecipeTest(t *testing.T) { + tests := []scaleRecipeTest{ + scaleRecipeTest{ + name: "scales up correctly", + input: []float64{0.5, 250, 150, 3, 0.5}, + portions: 6, + expected: []float64{1.5, 750, 450, 9, 1.5}, + }, + scaleRecipeTest{ + name: "scales up correctly (2)", + input: []float64{0.6, 300, 1, 0.5, 50, 0.1, 100}, + portions: 3, + expected: []float64{0.9, 450, 1.5, 0.75, 75, 0.15, 150}, + }, + scaleRecipeTest{ + name: "scales down correctly", + input: []float64{0.5, 250, 150, 3, 0.5}, + portions: 1, + expected: []float64{0.25, 125, 75, 1.5, 0.25}, + }, + scaleRecipeTest{ + name: "empty recipe", + input: []float64{}, + portions: 100, + expected: []float64{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inputList := make([]float64, len(tt.input)) + copy(inputList, tt.input) + got := ScaleRecipe(inputList, tt.portions) + if len(got) != len(tt.expected) { + t.Errorf("ScaleRecipe(%v, %d) produced slice of length %d, expected %d", inputList, tt.portions, len(got), len(tt.expected)) + } + for i := range tt.expected { + if math.Abs(got[i]-tt.expected[i]) > 0.000001 { + t.Errorf("Got %f Expected %f for index %d", got[i], tt.expected[i], i) + } + } + if !reflect.DeepEqual(inputList, tt.input) { + t.Errorf("ScaleRecipe permuted list (was %v, now %v), should not alter inputs", tt.input, inputList) + } + }) + } +}