├── LICENSE ├── README.md ├── exercises ├── 2.1 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── print.go │ └── print_test.go ├── 2.2 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── group.go │ └── group_test.go ├── 2.3 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── group.go │ └── group_test.go ├── 3.1 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── stringy.go │ └── stringy_test.go ├── 3.2 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── intish.go │ └── intish_test.go ├── 3.3 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── greater.go │ └── greater_test.go ├── 4.1 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── product.go │ └── product_test.go ├── 4.2 │ ├── README.md │ ├── dupes.go │ ├── dupes_test.go │ ├── go.mod │ └── go.sum ├── 5.1 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── sequence.go │ └── sequence_test.go ├── 6.1 │ ├── README.md │ ├── funcmap.go │ ├── funcmap_test.go │ ├── go.mod │ └── go.sum ├── 6.2 │ ├── README.md │ ├── compose.go │ ├── compose_test.go │ ├── go.mod │ └── go.sum ├── 7.1 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── stack.go │ └── stack_test.go ├── 8.1 │ ├── README.md │ ├── billable.go │ ├── billable_test.go │ ├── go.mod │ └── go.sum ├── 9.1 │ ├── README.md │ ├── contains.go │ ├── contains_test.go │ ├── go.mod │ └── go.sum ├── 9.2 │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── merge.go │ └── merge_test.go └── run_tests.sh ├── img └── cover_small.png └── solutions ├── 2.1 ├── go.mod ├── go.sum ├── print.go └── print_test.go ├── 2.2 ├── go.mod ├── go.sum ├── group.go └── group_test.go ├── 2.3 ├── go.mod ├── go.sum ├── group.go └── group_test.go ├── 3.1 ├── go.mod ├── go.sum ├── stringy.go └── stringy_test.go ├── 3.2 ├── go.mod ├── go.sum ├── intish.go └── intish_test.go ├── 3.3 ├── go.mod ├── go.sum ├── greater.go └── greater_test.go ├── 4.1 ├── go.mod ├── go.sum ├── product.go └── product_test.go ├── 4.2 ├── dupes.go ├── dupes_test.go ├── go.mod └── go.sum ├── 5.1 ├── go.mod ├── go.sum ├── sequence.go └── sequence_test.go ├── 6.1 ├── funcmap.go ├── funcmap_test.go ├── go.mod └── go.sum ├── 6.2 ├── compose.go ├── compose_test.go ├── go.mod └── go.sum ├── 7.1 ├── go.mod ├── go.sum ├── stack.go └── stack_test.go ├── 8.1 ├── billable.go ├── billable_test.go ├── go.mod └── go.sum ├── 9.1 ├── contains.go ├── contains_test.go ├── go.mod └── go.sum ├── 9.2 ├── go.mod ├── go.sum ├── merge.go └── merge_test.go └── run_tests.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 John Arundel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Know Go: Generics 2 | 3 | [![](img/cover_small.png)](https://bitfieldconsulting.com/books/generics) 4 | 5 | This repository contains exercises, solutions, and code samples from an older edition of the book [Know Go: Generics](https://bitfieldconsulting.com/books/generics), by John Arundel. 6 | 7 | For the current edition, please use [https://github.com/bitfield/kg-generics2](https://github.com/bitfield/kg-generics2) instead. 8 | 9 | ## 1. Generics 10 | 11 | There are no exercises for this chapter. You can relax! 12 | 13 | ## 2. Type parameters 14 | 15 | [Read an online version of the chapter](https://bitfieldconsulting.com/golang/type-parameters) 16 | 17 | 1. [Hello, generics](exercises/2.1/) 18 | 2. [Group therapy](exercises/2.2/) 19 | 3. [Lengthy proceedings](exercises/2.3/) 20 | 21 | ## 3. Constraints 22 | 23 | 1. [Stringy beans](exercises/3.1/) 24 | 2. [A first approximation](exercises/3.2/) 25 | 3. [Greater love](exercises/3.3/) 26 | 27 | ## 4. Operations 28 | 29 | 1. [Product placement](exercises/4.1/) 30 | 2. [Duplicate keys](exercises/4.2/) 31 | 32 | ## 5. Types 33 | 34 | 1. [Empty promises](exercises/5.1/) 35 | 36 | ## 6. Functions 37 | 38 | 1. [Func to funky](exercises/6.1/) 39 | 2. [Compose yourself](exercises/6.2/) 40 | 41 | ## 7. Containers 42 | 43 | 1. [Stack overflow](exercises/7.1/) 44 | 45 | ## 8. Concurrency 46 | 47 | 1. [Channelling frustration](exercises/8.1/) 48 | 49 | ## 9. Libraries 50 | 51 | 1. [Contain your excitement](exercises/9.1/) 52 | 2. [Merging in turn](exercises/9.2/) 53 | 54 | ## 10. Questions 55 | 56 | There are no exercises for this chapter. Why not enjoy a refreshing cup of tea? 57 | -------------------------------------------------------------------------------- /exercises/2.1/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 2.1: Hello, generics 2 | 3 | It's time to write your first generic function in Go! 4 | 5 | Take a look at the [`print_test.go`](print_test.go) file in this folder. You'll find the following test: 6 | 7 | ```go 8 | func TestPrintAnythingTo_PrintsInputToSuppliedWriter(t *testing.T) { 9 | t.Parallel() 10 | buf := &bytes.Buffer{} 11 | print.PrintAnythingTo[string](buf, "Hello, world") 12 | want := "Hello, world\n" 13 | got := buf.String() 14 | if !cmp.Equal(want, got) { 15 | t.Error(cmp.Diff(want, got)) 16 | } 17 | } 18 | ``` 19 | 20 | First, run the `go mod tidy` command to download the necessary modules for this test. Once that's completed, you're ready to start the exercise. 21 | 22 | ## Running the test 23 | 24 | Run the test using your editor, or the `go test` command. You'll find right now the test doesn't compile, because the required function doesn't exist: 25 | 26 | ``` 27 | undefined: print.PrintAnythingTo 28 | ``` 29 | 30 | Remember, you'll need at least Go version 1.18 to be able to use generics, including running this test and implementing the function to make it pass. That's because we're using some new syntax that doesn't exist in Go version 1.17 and earlier. 31 | 32 | If you try to compile this code with a version of Go that doesn't support generics, you'll get a rather confusing additional error message: 33 | 34 | ``` 35 | type string is not an expression 36 | ``` 37 | 38 | So if you see this, you need to upgrade your Go. 39 | 40 | ## Passing the test 41 | 42 | To make this test compile, you'll need to define a function in the `print` package named `PrintAnythingTo` that takes one parameter of type `io.Writer`, and another value that's of some unspecified type. 43 | 44 | To make the test _pass_, that function will need to write the supplied value to the supplied writer. It's up to you how to do this, but you might like to use `fmt.Fprintln`. 45 | 46 | The necessary `go.mod` and `print.go` files are already set up for you. All you need to do is edit `print.go` and add the `PrintAnythingTo` function, then run the test again. 47 | 48 | The main point of this exercise is to get you up and running writing generics in Go, and to get some practice declaring functions that take type parameters. When the test passes, you're done, so you can move on to the next exercise. 49 | 50 | If you get stuck, you can take a look at my suggested answer in [Solution 2.1](../../solutions/2.1/print.go). 51 | 52 | --- 53 | 54 | [Index](../../README.md) - [Next](../2.2/) -------------------------------------------------------------------------------- /exercises/2.1/go.mod: -------------------------------------------------------------------------------- 1 | module print 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /exercises/2.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 4 | -------------------------------------------------------------------------------- /exercises/2.1/print.go: -------------------------------------------------------------------------------- 1 | package print 2 | 3 | // Your PrintAnythingTo function goes here! 4 | -------------------------------------------------------------------------------- /exercises/2.1/print_test.go: -------------------------------------------------------------------------------- 1 | package print_test 2 | 3 | import ( 4 | "bytes" 5 | "print" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestPrintAnythingTo_PrintsInputToSuppliedWriter(t *testing.T) { 12 | t.Parallel() 13 | buf := &bytes.Buffer{} 14 | print.PrintAnythingTo[string](buf, "Hello, world") 15 | want := "Hello, world\n" 16 | got := buf.String() 17 | if !cmp.Equal(want, got) { 18 | t.Error(cmp.Diff(want, got)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /exercises/2.2/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 2.2: Group therapy 2 | 3 | This time, you'll need to define a generic slice type `Group[E]` to pass the following test: 4 | 5 | ```go 6 | func TestGroupContainsWhatIsAppendedToIt(t *testing.T) { 7 | t.Parallel() 8 | got := group.Group[string]{} 9 | got = append(got, "hello") 10 | got = append(got, "world") 11 | want := group.Group[string]{"hello", "world"} 12 | if !cmp.Equal(want, got) { 13 | t.Error(cmp.Diff(want, got)) 14 | } 15 | } 16 | ``` 17 | 18 | If you get stuck, have a look at [Solution 2.2](../../solutions/2.2/group.go). 19 | 20 | --- 21 | 22 | [Index](../../README.md) - [Next](../2.3/) -------------------------------------------------------------------------------- /exercises/2.2/go.mod: -------------------------------------------------------------------------------- 1 | module group 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /exercises/2.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/2.2/group.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | // Your Group type goes here! 4 | -------------------------------------------------------------------------------- /exercises/2.2/group_test.go: -------------------------------------------------------------------------------- 1 | package group_test 2 | 3 | import ( 4 | "group" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestGroupContainsWhatIsAppendedToIt(t *testing.T) { 11 | t.Parallel() 12 | got := group.Group[string]{} 13 | got = append(got, "hello") 14 | got = append(got, "world") 15 | want := group.Group[string]{"hello", "world"} 16 | if !cmp.Equal(want, got) { 17 | t.Error(cmp.Diff(want, got)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /exercises/2.3/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 2.3: Lengthy proceedings 2 | 3 | Your task is to write a generic function `Len[E]` that returns the length of a given slice. As usual, the supplied test will tell you when you've got it right! 4 | 5 | Have fun, and if you run into difficulties, check out [Solution 2.3](../../solutions/2.3/group.go). 6 | 7 | --- 8 | 9 | [Index](../../README.md) - [Next](../3.1/) -------------------------------------------------------------------------------- /exercises/2.3/go.mod: -------------------------------------------------------------------------------- 1 | module group 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /exercises/2.3/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/2.3/group.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | type Group[E any] []E 4 | 5 | // Your Len function goes here! 6 | -------------------------------------------------------------------------------- /exercises/2.3/group_test.go: -------------------------------------------------------------------------------- 1 | package group_test 2 | 3 | import ( 4 | "group" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestLenOfGroupIs2WhenItContains2Elements(t *testing.T) { 11 | t.Parallel() 12 | g := group.Group[int]{1, 2} 13 | want := 2 14 | got := group.Len(g) 15 | if !cmp.Equal(want, got) { 16 | t.Error(cmp.Diff(want, got)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /exercises/3.1/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 3.1: Stringy beans 2 | 3 | Flex your generics muscles a little now, by writing a generic function constrained by `fmt.Stringer`. 4 | 5 | Your job here is to write a generic function `StringifyTo[T]` that takes an `io.Writer` and a value of some arbitrary type constrained by `fmt.Stringer`, and prints the value to the writer. 6 | 7 | If you're not sure what to do, peek at [Solution 3.1](../../solutions/3.1/stringy.go) for hints. 8 | 9 | --- 10 | 11 | [Index](../../README.md) - [Next](../3.2/) -------------------------------------------------------------------------------- /exercises/3.1/go.mod: -------------------------------------------------------------------------------- 1 | module stringy 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /exercises/3.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/3.1/stringy.go: -------------------------------------------------------------------------------- 1 | package stringy 2 | 3 | // Your StringifyTo function goes here! 4 | -------------------------------------------------------------------------------- /exercises/3.1/stringy_test.go: -------------------------------------------------------------------------------- 1 | package stringy_test 2 | 3 | import ( 4 | "bytes" 5 | "stringy" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | type greeting struct{} 12 | 13 | func (greeting) String() string { 14 | return "Howdy!" 15 | } 16 | 17 | func TestStringifyTo_PrintsResultOfStringMethodToSuppliedWriter(t *testing.T) { 18 | t.Parallel() 19 | buf := &bytes.Buffer{} 20 | stringy.StringifyTo[greeting](buf, greeting{}) 21 | want := "Howdy!\n" 22 | got := buf.String() 23 | if !cmp.Equal(want, got) { 24 | t.Error(cmp.Diff(want, got)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /exercises/3.2/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 3.2: A first approximation 2 | 3 | Here you're provided with a function `IsPositive`, which determines whether a given value is greater than zero, and a set of accompanying tests. 4 | 5 | The `IsPositive` function is constrained by some interface `Intish`, and the tests call it with values of the following type: 6 | 7 | ```go 8 | type MyInt int 9 | ``` 10 | 11 | Your task here is to define the `Intish` interface. A method set won't work, because `int` has no methods. On the other hand, the type literal `int` won't work either, because `MyInt` is not `int`. 12 | 13 | What could you use instead? 14 | 15 | If you'd like some clues, have a look at [Solution 3.2](../../solutions/3.2/intish.go). 16 | 17 | --- 18 | 19 | [Index](../../README.md) - [Next](../3.3/) 20 | -------------------------------------------------------------------------------- /exercises/3.2/go.mod: -------------------------------------------------------------------------------- 1 | module intish 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /exercises/3.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/3.2/intish.go: -------------------------------------------------------------------------------- 1 | package intish 2 | 3 | // Your Intish interface goes here! 4 | 5 | func IsPositive[T Intish](v T) bool { 6 | return v > 0 7 | } 8 | -------------------------------------------------------------------------------- /exercises/3.2/intish_test.go: -------------------------------------------------------------------------------- 1 | package intish_test 2 | 3 | import ( 4 | "intish" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | type MyInt int 11 | 12 | func TestIsPositive_IsTrueFor1(t *testing.T) { 13 | t.Parallel() 14 | input := MyInt(1) 15 | want := true 16 | got := intish.IsPositive(input) 17 | if !cmp.Equal(want, got) { 18 | t.Error(cmp.Diff(want, got)) 19 | } 20 | } 21 | 22 | func TestIsPositive_IsFalseForNegative1(t *testing.T) { 23 | t.Parallel() 24 | input := MyInt(-1) 25 | want := false 26 | got := intish.IsPositive(input) 27 | if !cmp.Equal(want, got) { 28 | t.Error(cmp.Diff(want, got)) 29 | } 30 | } 31 | 32 | func TestIsPositive_IsFalseForZero(t *testing.T) { 33 | t.Parallel() 34 | input := MyInt(0) 35 | want := false 36 | got := intish.IsPositive(input) 37 | if !cmp.Equal(want, got) { 38 | t.Error(cmp.Diff(want, got)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /exercises/3.3/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 3.3: Greater love 2 | 3 | You've been given the following (incomplete) function: 4 | 5 | ```go 6 | func IsGreater[T /* Your constraint goes here! */](x, y T) bool { 7 | return x.Greater(y) 8 | } 9 | ``` 10 | 11 | This takes two values of some arbitrary type, and compares them by calling the `Greater` method on the first value, passing it the second value. 12 | 13 | The tests exercise this function by calling it with two values of a defined type `MyInt`, which has the required `Greater` method. So to make these tests pass, you'll need to write an appropriate type constraint for `IsGreater`. 14 | 15 | Can you see what to do? 16 | 17 | You can check your answer against [Solution 3.3](../../solutions/3.3/greater.go). 18 | 19 | --- 20 | 21 | [Index](../../README.md) - [Next](../4.1/) 22 | -------------------------------------------------------------------------------- /exercises/3.3/go.mod: -------------------------------------------------------------------------------- 1 | module greater 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /exercises/3.3/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/3.3/greater.go: -------------------------------------------------------------------------------- 1 | package greater 2 | 3 | func IsGreater[T /* Your constraint goes here! */](x, y T) bool { 4 | return x.Greater(y) 5 | } 6 | -------------------------------------------------------------------------------- /exercises/3.3/greater_test.go: -------------------------------------------------------------------------------- 1 | package greater_test 2 | 3 | import ( 4 | "greater" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | type MyInt int 11 | 12 | func (m MyInt) Greater(v MyInt) bool { 13 | return m > v 14 | } 15 | 16 | func TestIsGreater_IsTrueFor2And1(t *testing.T) { 17 | t.Parallel() 18 | want := true 19 | got := greater.IsGreater(MyInt(2), MyInt(1)) 20 | if !cmp.Equal(want, got) { 21 | t.Error(cmp.Diff(want, got)) 22 | } 23 | } 24 | 25 | func TestIsGreater_IsFalseFor1And2(t *testing.T) { 26 | t.Parallel() 27 | want := false 28 | got := greater.IsGreater(MyInt(1), MyInt(2)) 29 | if !cmp.Equal(want, got) { 30 | t.Error(cmp.Diff(want, got)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /exercises/4.1/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 4.1: Product placement 2 | 3 | Your job is to define a function `Product`, which will return the result of multiplying its two inputs. For example: 4 | 5 | ```go 6 | fmt.Println(product.Product(2, 3)) 7 | // 6 8 | ``` 9 | 10 | You'll also need to define a suitable type constraint for this function. Make sure it's broad enough to allow all the different numeric types supplied by the tests, but not _so_ broad that it doesn't allow you to implement the function! 11 | 12 | If you have any trouble, refer to [Solution 4.1](../../solutions/4.1/product.go) for one possible answer. 13 | 14 | --- 15 | 16 | [Index](../../README.md) - [Next](../4.2/) 17 | -------------------------------------------------------------------------------- /exercises/4.1/go.mod: -------------------------------------------------------------------------------- 1 | module product 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | 7 | require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect 8 | -------------------------------------------------------------------------------- /exercises/4.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/4.1/product.go: -------------------------------------------------------------------------------- 1 | package product 2 | 3 | // Your constraint definition goes here! 4 | 5 | // Your Product function goes here! 6 | -------------------------------------------------------------------------------- /exercises/4.1/product_test.go: -------------------------------------------------------------------------------- 1 | package product_test 2 | 3 | import ( 4 | "product" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | "github.com/google/go-cmp/cmp/cmpopts" 9 | ) 10 | 11 | func TestProductOfInts2And3Is6(t *testing.T) { 12 | t.Parallel() 13 | want := 6 14 | got := product.Product(2, 3) 15 | if !cmp.Equal(want, got) { 16 | t.Error(cmp.Diff(want, got)) 17 | } 18 | } 19 | 20 | func TestProductOfFloats1Point6And2Point3Is3Point68(t *testing.T) { 21 | t.Parallel() 22 | want := 3.68 23 | got := product.Product(1.6, 2.3) 24 | if !cmp.Equal(want, got, cmpopts.EquateApprox(0.0001, 0)) { 25 | t.Error(cmp.Diff(want, got)) 26 | } 27 | } 28 | 29 | func TestProductOfComplex2Plus3iAnd3Plus2iIs0Plus13i(t *testing.T) { 30 | t.Parallel() 31 | want := 0 + 13i 32 | got := product.Product(2+3i, 3+2i) 33 | if !cmp.Equal(want, got) { 34 | t.Error(cmp.Diff(want, got)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /exercises/4.2/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 4.2: Duplicate keys 2 | 3 | The task here is to determine if a given slice contains duplicated elements. For example, if a slice of numbers contains any given number more than once, then the slice contains duplicates. The duplicate elements need not be consecutive. 4 | 5 | To do this, you'll need to write a function `Dupes`, with an appropriate type constraint, that returns `true` if the given slice contains duplicates, or `false` otherwise. 6 | 7 | For example: 8 | 9 | ```go 10 | fmt.Println(Dupes([]int{1, 2, 3}] 11 | // false 12 | fmt.Println(Dupes([]int{1, 2, 1}] 13 | // true 14 | ``` 15 | 16 | See [Solution 4.2](../../solutions/4.2/dupes.go) if you'd like something to compare with your answer. 17 | 18 | --- 19 | 20 | [Index](../../README.md) - [Next](../5.1/) 21 | -------------------------------------------------------------------------------- /exercises/4.2/dupes.go: -------------------------------------------------------------------------------- 1 | package dupes 2 | 3 | // Your Dupes function goes here! 4 | -------------------------------------------------------------------------------- /exercises/4.2/dupes_test.go: -------------------------------------------------------------------------------- 1 | package dupes_test 2 | 3 | import ( 4 | "dupes" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestDupesIsTrueWhenInputContainsNonConsecutiveDuplicates(t *testing.T) { 11 | t.Parallel() 12 | s := []int{1, 2, 3, 1, 5} 13 | want := true 14 | got := dupes.Dupes(s) 15 | if !cmp.Equal(want, got) { 16 | t.Error(cmp.Diff(want, got)) 17 | } 18 | } 19 | 20 | func TestDupesIsFalseWhenInputContainsNoDuplicates(t *testing.T) { 21 | t.Parallel() 22 | s := []string{"a"} 23 | want := false 24 | got := dupes.Dupes(s) 25 | if !cmp.Equal(want, got) { 26 | t.Error(cmp.Diff(want, got)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /exercises/4.2/go.mod: -------------------------------------------------------------------------------- 1 | module dupes 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /exercises/4.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/5.1/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 5.1: Empty promises 2 | 3 | Your job is to create a `Sequence` type that can hold multiple values, and an `Empty` method on it that will report whether or not the sequence is empty. 4 | 5 | For example: 6 | 7 | ```go 8 | s := Sequence[string]{"a", "b", "c"} 9 | fmt.Println(s.Empty()) 10 | // false 11 | ``` 12 | 13 | See [Solution 5.1](../../solutions/5.1/sequence.go) if you'd like something to compare with your answer. 14 | 15 | --- 16 | 17 | [Index](../../README.md) - [Next](../6.1/) 18 | -------------------------------------------------------------------------------- /exercises/5.1/go.mod: -------------------------------------------------------------------------------- 1 | module sequence 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /exercises/5.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/5.1/sequence.go: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | // Your implementation of Sequence goes here! 4 | -------------------------------------------------------------------------------- /exercises/5.1/sequence_test.go: -------------------------------------------------------------------------------- 1 | package sequence_test 2 | 3 | import ( 4 | "sequence" 5 | "testing" 6 | ) 7 | 8 | func TestEmptyIsTrueForEmptySequence(t *testing.T) { 9 | t.Parallel() 10 | s := sequence.Sequence[int]{} 11 | got := s.Empty() 12 | if !got { 13 | t.Fatal("false for empty sequence") 14 | } 15 | } 16 | 17 | func TestEmptyIsFalseForNonEmptySequence(t *testing.T) { 18 | t.Parallel() 19 | s := sequence.Sequence[string]{"a", "b", "c"} 20 | got := s.Empty() 21 | if got { 22 | t.Fatal("true for non-empty sequence") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /exercises/6.1/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 6.1: Func to funky 2 | 3 | For this exercise, you'll be building a simple _dynamic dispatch_ system in Go. What's required here is a way of storing a number of different functions by name, and then applying the one you want. 4 | 5 | Your job is to create a `FuncMap` type which can store a number of named functions. That is, it's a map of `string` to some arbitrary `func` type. For example: 6 | 7 | ```go 8 | fm := FuncMap[int, int]{ 9 | "double": func(i int) int { 10 | return i * 2 11 | }, 12 | "addOne": func(i int) int { 13 | return i + 1 14 | }, 15 | } 16 | ``` 17 | 18 | You'll also need to implement an `Apply` method on `FuncMap` which will apply the specified function to some value and return the result. For example: 19 | 20 | ```go 21 | fmt.Println(fm.Apply("double", 2)) 22 | // 4 23 | ``` 24 | 25 | If it stops being fun, take a look at one possible answer in [Solution 6.1](../../solutions/6.1/funcmap.go). 26 | 27 | --- 28 | 29 | [Index](../../README.md) - [Next](../6.2/) 30 | -------------------------------------------------------------------------------- /exercises/6.1/funcmap.go: -------------------------------------------------------------------------------- 1 | package funcmap 2 | 3 | // Your implementation of FuncMap goes here! 4 | -------------------------------------------------------------------------------- /exercises/6.1/funcmap_test.go: -------------------------------------------------------------------------------- 1 | package funcmap_test 2 | 3 | import ( 4 | "funcmap" 5 | "testing" 6 | "unicode" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestFuncMap_AppliesDoubleTo2AndReturns4(t *testing.T) { 12 | t.Parallel() 13 | fm := funcmap.FuncMap[int, int]{ 14 | "double": func(i int) int { 15 | return i * 2 16 | }, 17 | "addOne": func(i int) int { 18 | return i + 1 19 | }, 20 | } 21 | want := 4 22 | got := fm.Apply("double", 2) 23 | if !cmp.Equal(want, got) { 24 | t.Error(cmp.Diff(want, got)) 25 | } 26 | } 27 | 28 | func TestFuncMap_AppliesUpperToUppercaseInputAndReturnsTrue(t *testing.T) { 29 | t.Parallel() 30 | fm := funcmap.FuncMap[rune, bool]{ 31 | "upper": unicode.IsUpper, 32 | "lower": unicode.IsLower, 33 | } 34 | got := fm.Apply("upper", 'A') 35 | if !got { 36 | t.Fatal("upper('A'): want true, got false") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /exercises/6.1/go.mod: -------------------------------------------------------------------------------- 1 | module funcmap 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /exercises/6.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/6.2/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 6.2: Compose yourself 2 | 3 | _Composing_ functions means applying them in a chain, so that each function operates on the result of the previous one. 4 | 5 | For example, if we had two arbitrary functions `outer` and `inner`, then we could compose them directly by writing: 6 | 7 | ```go 8 | outer(inner()) 9 | ``` 10 | 11 | Your job in this exercise is to write a `Compose` function that will do just this. Given two functions, `outer` and `inner`, and a value of arbitrary type, it should first apply `inner` to the value, then apply `outer` to the result returned by `inner`, then return the result returned by `outer`. 12 | 13 | Got all that? Here's an example. Suppose we had a function `double` that returns double its input, and another function `addOne` that returns the result of adding 1 to its input. 14 | 15 | And suppose our input value is 1. If we first apply `addOne` to 1, we should get 2. If we then apply `double` to 2, we should get 4. So: 16 | 17 | ```go 18 | fmt.Println(Compose(double, addOne, 1)) 19 | // 4 20 | ``` 21 | 22 | What if we composed the functions in reverse order, so that we applied `double` first, then `addOne` to the result? Well: 23 | 24 | ```go 25 | fmt.Println(Compose(addOne, double, 1)) 26 | // 3 27 | ``` 28 | 29 | If you're having trouble with this, remember that the `inner` function will need to take a parameter of the same type as the value, and its result type is arbitrary. The `outer` function will need to take a parameter of whatever type `inner` returns, but _its_ result type is also arbitrary. 30 | 31 | So, all told, there are _three_ type parameters involved here. 32 | 33 | If you're still stuck, have a look at one possible way of doing it in [Solution 6.2](../../solutions/6.2/compose.go). 34 | 35 | --- 36 | 37 | [Index](../../README.md) - [Next](../7.1/) 38 | -------------------------------------------------------------------------------- /exercises/6.2/compose.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | // Your implementation of Compose goes here! 4 | -------------------------------------------------------------------------------- /exercises/6.2/compose_test.go: -------------------------------------------------------------------------------- 1 | package compose_test 2 | 3 | import ( 4 | "compose" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func isOdd(p int) bool { 12 | return p%2 != 0 13 | } 14 | 15 | func next(p int) int { 16 | return p + 1 17 | } 18 | 19 | func TestComposeAppliesFuncsToIntInReverseOrder(t *testing.T) { 20 | t.Parallel() 21 | odd := compose.Compose(isOdd, next, 1) 22 | if odd { 23 | t.Fatal("isOdd(next(1)): want false, got true") 24 | } 25 | } 26 | 27 | func TestComposeAppliesFuncsToStringInReverseOrder(t *testing.T) { 28 | t.Parallel() 29 | input := "HeLlO, wOrLd" 30 | want := "hello, world" 31 | got := compose.Compose(strings.ToLower, strings.ToUpper, input) 32 | if !cmp.Equal(want, got) { 33 | t.Error(cmp.Diff(want, got)) 34 | } 35 | } 36 | 37 | func first[E any](s []E) E { 38 | return s[0] 39 | } 40 | 41 | func last[E any](s []E) E { 42 | return s[len(s)-1] 43 | } 44 | 45 | func TestComposeAppliesFuncsToSliceInReverseOrder(t *testing.T) { 46 | t.Parallel() 47 | input := [][]int{{1, 2, 3}} 48 | want := 1 49 | got := compose.Compose(first[int], last[[]int], input) 50 | if !cmp.Equal(want, got) { 51 | t.Error(cmp.Diff(want, got)) 52 | } 53 | } 54 | 55 | func TestComposeAppliesFuncsToSliceInReverseOrder2(t *testing.T) { 56 | t.Parallel() 57 | input := [][]int{{1, 2, 3}} 58 | want := 3 59 | got := compose.Compose(last[int], first[[]int], input) 60 | if !cmp.Equal(want, got) { 61 | t.Error(cmp.Diff(want, got)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /exercises/6.2/go.mod: -------------------------------------------------------------------------------- 1 | module compose 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /exercises/6.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/7.1/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 7.1: Stack overflow 2 | 3 | A [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) is an abstract data type which stores items in a last-in-first-out way. The only thing you can do with a stack is "push" a new item onto it, or "pop" the top item off it. 4 | 5 | Your task here is to define a generic `Stack` type that holds an ordered collection of values of as broad a range of types as possible. 6 | 7 | You'll need to write a `Push` method that can append any number of items to the stack, in order, and a `Pop` method that retrieves (and removes) the last item from the stack. 8 | 9 | `Pop` should also return a second `ok` value indicating whether an item was actually popped. If the stack is empty, then `Pop` should return `false` for this second value, but `true` otherwise. 10 | 11 | Also, you should provide a `Len` method that returns the number of items in the stack. 12 | 13 | For example: 14 | 15 | ```go 16 | s := Stack[int]{} 17 | fmt.Println(s.Pop()) 18 | // 0 false 19 | s.Push(5, 6) 20 | fmt.Println(s.Len()) 21 | // 2 22 | v, ok := s.Pop() 23 | fmt.Println(v) 24 | // 6 25 | fmt.Println(ok) 26 | // true 27 | ``` 28 | 29 | If you'd like some hints, check out [Solution 7.1](../../solutions/7.1/stack.go) for one possible way to solve this. 30 | 31 | --- 32 | 33 | [Index](../../README.md) - [Next](../8.1/) 34 | -------------------------------------------------------------------------------- /exercises/7.1/go.mod: -------------------------------------------------------------------------------- 1 | module stack 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /exercises/7.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/7.1/stack.go: -------------------------------------------------------------------------------- 1 | package stack 2 | 3 | // Your implementation of Stack goes here! 4 | -------------------------------------------------------------------------------- /exercises/7.1/stack_test.go: -------------------------------------------------------------------------------- 1 | package stack_test 2 | 3 | import ( 4 | "stack" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestPushOneValueToEmptyStackLeavesTheStackWithLength1(t *testing.T) { 11 | t.Parallel() 12 | s := stack.Stack[int]{} 13 | s.Push(0) 14 | if s.Len() != 1 { 15 | t.Fatal("Push didn't add value to stack") 16 | } 17 | } 18 | 19 | func TestPushTwiceThenPopTwiceOnEmptyStackLeavesTheStackEmpty(t *testing.T) { 20 | t.Parallel() 21 | s := stack.Stack[string]{} 22 | s.Push("a", "b") 23 | if s.Len() != 2 { 24 | t.Fatal("Push didn't add all values to stack") 25 | } 26 | s.Pop() 27 | want := "a" 28 | got, ok := s.Pop() 29 | if !ok { 30 | t.Fatal("Pop returned not ok on non-empty stack") 31 | } 32 | if !cmp.Equal(want, got) { 33 | t.Error(cmp.Diff(want, got)) 34 | } 35 | } 36 | 37 | func TestPopReturnsNotOKOnEmptyStack(t *testing.T) { 38 | t.Parallel() 39 | s := stack.Stack[int]{} 40 | _, ok := s.Pop() 41 | if ok { 42 | t.Fatal("Pop returned ok on empty stack") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /exercises/8.1/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 8.1: Channelling frustration 2 | 3 | It seems people have been using channels in Go a little too freely, and a scheme is now proposed to bill users by activity. Accordingly, you'll need to define a generic `Channel` type with suitable instrumentation to track the number of sends and receives. 4 | 5 | You have the following tasks to complete: 6 | 7 | 1. Define the `Channel` type. 8 | 2. Define a `NewChannel` function that returns an initialised `Channel` with a specified capacity, ready for use. 9 | 3. Define `Send` and `Receive` methods on the `Channel` to allow users to send and receive values of the appropriate type. 10 | 4. Define `Sends` and `Receives` methods that will report the number of sends and receives on the channel, for billing purposes. 11 | 12 | For example: 13 | 14 | ```go 15 | c := NewChannel[string](1) 16 | c.Send("hello") 17 | fmt.Println(c.Sends()) 18 | // 1 19 | fmt.Println(c.Receive()) 20 | // hello 21 | fmt.Println(c.Receives()) 22 | // 1 23 | ``` 24 | 25 | You'll need to ensure that your `Channel` is concurrency-safe, so make sure your solution passes the tests when the race detector is enabled: 26 | 27 | **`go test -race`** 28 | 29 | See [Solution 8.1](../../solutions/8.1/billable.go) if you'd like something to compare with your answer. 30 | 31 | --- 32 | 33 | [Index](../../README.md) - [Next](../9.1/) 34 | -------------------------------------------------------------------------------- /exercises/8.1/billable.go: -------------------------------------------------------------------------------- 1 | package billable 2 | 3 | // Your implementation of Channel goes here!! 4 | -------------------------------------------------------------------------------- /exercises/8.1/billable_test.go: -------------------------------------------------------------------------------- 1 | package billable_test 2 | 3 | import ( 4 | "billable" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestSendFollowedByReceiveGivesOriginalValue(t *testing.T) { 12 | t.Parallel() 13 | c := billable.NewChannel[int](1) 14 | want := 99 15 | c.Send(want) 16 | got := c.Receive() 17 | if !cmp.Equal(want, got) { 18 | t.Fatal(cmp.Diff(want, got)) 19 | } 20 | } 21 | 22 | func TestSendsReturns3AfterThreeSendOperations(t *testing.T) { 23 | t.Parallel() 24 | c := billable.NewChannel[float64](3) 25 | c.Send(1.0) 26 | c.Send(2.0) 27 | c.Send(3.0) 28 | want := 3 29 | got := c.Sends() 30 | if want != got { 31 | t.Fatalf("want %d sends, got %d", want, got) 32 | } 33 | } 34 | 35 | func TestReceivesReturns2AfterTwoSendOperations(t *testing.T) { 36 | t.Parallel() 37 | c := billable.NewChannel[struct{}](1) 38 | c.Send(struct{}{}) 39 | _ = c.Receive() 40 | c.Send(struct{}{}) 41 | _ = c.Receive() 42 | want := 2 43 | got := c.Receives() 44 | if want != got { 45 | t.Fatalf("want 2 receives, got %d", got) 46 | } 47 | } 48 | 49 | func TestChannelDoesNotPanicOnManyConcurrentSendsAndReceives(t *testing.T) { 50 | t.Parallel() 51 | c := billable.NewChannel[string](10) 52 | want := 100 53 | var wg sync.WaitGroup 54 | wg.Add(1) 55 | go func() { 56 | for i := 0; i < want; i++ { 57 | c.Send("hello") 58 | _ = c.Receives() 59 | } 60 | wg.Done() 61 | }() 62 | for i := 0; i < want; i++ { 63 | _ = c.Receive() 64 | _ = c.Sends() 65 | } 66 | wg.Wait() 67 | got := c.Sends() 68 | if want != got { 69 | t.Errorf("want %d sends, got %d", want, got) 70 | } 71 | got = c.Receives() 72 | if want != got { 73 | t.Errorf("want %d receives, got %d", want, got) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /exercises/8.1/go.mod: -------------------------------------------------------------------------------- 1 | module billable 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /exercises/8.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /exercises/9.1/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 9.1: Contain your excitement 2 | 3 | Your job here is to write a generic function `ContainsFunc` that operates on slices of arbitrary type. Given a function argument, `ContainsFunc` should return true if the function returns true for any element in the slice, or false otherwise. 4 | 5 | For example: 6 | 7 | ```go 8 | s := []rune{'a', 'B', 'c'} 9 | fmt.Println(ContainsFunc(s, unicode.IsUpper)) 10 | // true 11 | ``` 12 | 13 | See [Solution 9.1](../../solutions/9.1/contains.go) if you'd like something to compare with your answer. 14 | 15 | --- 16 | 17 | [Index](../../README.md) - [Next](../9.2/) 18 | -------------------------------------------------------------------------------- /exercises/9.1/contains.go: -------------------------------------------------------------------------------- 1 | package contains 2 | 3 | import "golang.org/x/exp/slices" 4 | 5 | // Your implementation of ContainsFunc goes here! 6 | -------------------------------------------------------------------------------- /exercises/9.1/contains_test.go: -------------------------------------------------------------------------------- 1 | package contains_test 2 | 3 | import ( 4 | "contains" 5 | "testing" 6 | "unicode" 7 | ) 8 | 9 | func positive(p int) bool { 10 | return p > 0 11 | } 12 | 13 | func TestContainsFunc_IsTrueForPositiveOnInputContainingPositiveInts(t *testing.T) { 14 | t.Parallel() 15 | input := []int{-2, 0, 1, -1, 5} 16 | got := contains.ContainsFunc(input, positive) 17 | if !got { 18 | t.Fatalf("%v: want true for 'contains positive', got false", input) 19 | } 20 | } 21 | 22 | func TestContainsFunc_IsFalseForIsUpperOnInputContainingNoUppercaseRunes(t *testing.T) { 23 | t.Parallel() 24 | input := []rune("hello") 25 | got := contains.ContainsFunc(input, unicode.IsUpper) 26 | if got { 27 | t.Fatalf("%q: want false for 'contains uppercase', got true", input) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /exercises/9.1/go.mod: -------------------------------------------------------------------------------- 1 | module contains 2 | 3 | go 1.18 4 | 5 | require golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 6 | -------------------------------------------------------------------------------- /exercises/9.1/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f h1:u4dL7EmDaaJ+1e0HD9rawKa15yKPQZWXQ/epCOPAU+A= 2 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f/go.mod h1:M50CtfS+xv2iy/epuEazynj250ScQ0/DOjcsin9UE8k= 3 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= 4 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= 5 | -------------------------------------------------------------------------------- /exercises/9.2/README.md: -------------------------------------------------------------------------------- 1 | # Exercise 9.2: Merging in turn 2 | 3 | This task is about writing a `Merge` function that takes any number of maps (all of the same arbitrary type) and merges them together, returning the result. 4 | 5 | In other words, the result returned by `Merge` is a map containing all the key-value pairs in all its arguments. 6 | 7 | For example: 8 | 9 | ```go 10 | m1 := map[int]bool{ 1: true } 11 | m2 := map[int]bool{ 2: true } 12 | fmt.Println(Merge(m1, m2)) 13 | // map[1:true 2:true] 14 | ``` 15 | 16 | If there are any conflicts (that is, if the maps have any key in common), then the "later" map wins. In other words, the maps are merged in the order that they're passed to `Merge`. For example: 17 | 18 | ```go 19 | m1 := map[int]bool{ 1: false } 20 | m2 := map[int]bool{ 1: true } 21 | fmt.Println(Merge(m1, m2)) 22 | // map[1:true] 23 | ``` 24 | 25 | If you'd like to see one way of solving this, take a peek at [Solution 9.2](../../solutions/9.2/merge.go). 26 | 27 | --- 28 | 29 | [Index](../../README.md) 30 | -------------------------------------------------------------------------------- /exercises/9.2/go.mod: -------------------------------------------------------------------------------- 1 | module merge 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/google/go-cmp v0.5.6 7 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f 8 | ) 9 | -------------------------------------------------------------------------------- /exercises/9.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f h1:u4dL7EmDaaJ+1e0HD9rawKa15yKPQZWXQ/epCOPAU+A= 4 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f/go.mod h1:M50CtfS+xv2iy/epuEazynj250ScQ0/DOjcsin9UE8k= 5 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 6 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 7 | -------------------------------------------------------------------------------- /exercises/9.2/merge.go: -------------------------------------------------------------------------------- 1 | package merge 2 | 3 | import "golang.org/x/exp/maps" 4 | 5 | // Your implementation of Merge goes here! 6 | -------------------------------------------------------------------------------- /exercises/9.2/merge_test.go: -------------------------------------------------------------------------------- 1 | package merge_test 2 | 3 | import ( 4 | "merge" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestMergeCorrectlyMergesTwoMapsOfIntToBool(t *testing.T) { 11 | t.Parallel() 12 | inputs := []map[int]bool{ 13 | { 14 | 1: false, 15 | 2: false, 16 | 3: false, 17 | }, 18 | { 19 | 3: true, 20 | 5: true, 21 | }, 22 | } 23 | want := map[int]bool{ 24 | 1: false, 25 | 2: false, 26 | 3: true, 27 | 5: true, 28 | } 29 | got := merge.Merge(inputs...) 30 | if !cmp.Equal(want, got) { 31 | t.Error(cmp.Diff(want, got)) 32 | } 33 | } 34 | 35 | func TestMergeCorrectlyMergesThreeMapsOfStringToAny(t *testing.T) { 36 | t.Parallel() 37 | inputs := []map[string]any{ 38 | { 39 | "a": nil, 40 | }, 41 | { 42 | "b": "hello, world", 43 | "c": 0, 44 | }, 45 | { 46 | "a": 6 + 2i, 47 | }, 48 | } 49 | want := map[string]any{ 50 | "a": 6 + 2i, 51 | "b": "hello, world", 52 | "c": 0, 53 | } 54 | got := merge.Merge(inputs...) 55 | if !cmp.Equal(want, got) { 56 | t.Error(cmp.Diff(want, got)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /exercises/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for file in ./*; do 4 | if [ -d "$file" ] ; then 5 | echo Testing $file... 6 | cd $file 7 | go test 8 | cd .. 9 | fi 10 | done 11 | -------------------------------------------------------------------------------- /img/cover_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfield/kg-generics/833ca506d81455e0d67989cb5011a05aa99356d0/img/cover_small.png -------------------------------------------------------------------------------- /solutions/2.1/go.mod: -------------------------------------------------------------------------------- 1 | module print 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /solutions/2.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 4 | -------------------------------------------------------------------------------- /solutions/2.1/print.go: -------------------------------------------------------------------------------- 1 | package print 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | func PrintAnythingTo[T any](w io.Writer, p T) { 9 | fmt.Fprintln(w, p) 10 | } 11 | -------------------------------------------------------------------------------- /solutions/2.1/print_test.go: -------------------------------------------------------------------------------- 1 | package print_test 2 | 3 | import ( 4 | "bytes" 5 | "print" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestPrintAnythingTo_PrintsInputToSuppliedWriter(t *testing.T) { 12 | t.Parallel() 13 | buf := &bytes.Buffer{} 14 | print.PrintAnythingTo[string](buf, "Hello, world") 15 | want := "Hello, world\n" 16 | got := buf.String() 17 | if !cmp.Equal(want, got) { 18 | t.Error(cmp.Diff(want, got)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /solutions/2.2/go.mod: -------------------------------------------------------------------------------- 1 | module group 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /solutions/2.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/2.2/group.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | type Group[E any] []E 4 | -------------------------------------------------------------------------------- /solutions/2.2/group_test.go: -------------------------------------------------------------------------------- 1 | package group_test 2 | 3 | import ( 4 | "group" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestGroupContainsWhatIsAppendedToIt(t *testing.T) { 11 | t.Parallel() 12 | got := group.Group[string]{} 13 | got = append(got, "hello") 14 | got = append(got, "world") 15 | want := group.Group[string]{"hello", "world"} 16 | if !cmp.Equal(want, got) { 17 | t.Error(cmp.Diff(want, got)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /solutions/2.3/go.mod: -------------------------------------------------------------------------------- 1 | module group 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /solutions/2.3/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/2.3/group.go: -------------------------------------------------------------------------------- 1 | package group 2 | 3 | type Group[E any] []E 4 | 5 | func Len[E any](s []E) int { 6 | return len(s) 7 | } 8 | -------------------------------------------------------------------------------- /solutions/2.3/group_test.go: -------------------------------------------------------------------------------- 1 | package group_test 2 | 3 | import ( 4 | "group" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestLenOfGroupIs2WhenItContains2Elements(t *testing.T) { 11 | t.Parallel() 12 | g := group.Group[int]{1, 2} 13 | want := 2 14 | got := group.Len(g) 15 | if !cmp.Equal(want, got) { 16 | t.Error(cmp.Diff(want, got)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /solutions/3.1/go.mod: -------------------------------------------------------------------------------- 1 | module stringy 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /solutions/3.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/3.1/stringy.go: -------------------------------------------------------------------------------- 1 | package stringy 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | func StringifyTo[T fmt.Stringer](w io.Writer, p T) { 9 | fmt.Fprintln(w, p.String()) 10 | } 11 | -------------------------------------------------------------------------------- /solutions/3.1/stringy_test.go: -------------------------------------------------------------------------------- 1 | package stringy_test 2 | 3 | import ( 4 | "bytes" 5 | "stringy" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | type greeting struct{} 12 | 13 | func (greeting) String() string { 14 | return "Howdy!" 15 | } 16 | 17 | func TestStringifyTo_PrintsResultOfStringMethodToSuppliedWriter(t *testing.T) { 18 | t.Parallel() 19 | buf := &bytes.Buffer{} 20 | stringy.StringifyTo[greeting](buf, greeting{}) 21 | want := "Howdy!\n" 22 | got := buf.String() 23 | if !cmp.Equal(want, got) { 24 | t.Error(cmp.Diff(want, got)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /solutions/3.2/go.mod: -------------------------------------------------------------------------------- 1 | module intish 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /solutions/3.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/3.2/intish.go: -------------------------------------------------------------------------------- 1 | package intish 2 | 3 | type Intish interface { 4 | ~int 5 | } 6 | 7 | func IsPositive[T Intish](v T) bool { 8 | return v > 0 9 | } 10 | -------------------------------------------------------------------------------- /solutions/3.2/intish_test.go: -------------------------------------------------------------------------------- 1 | package intish_test 2 | 3 | import ( 4 | "intish" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | type MyInt int 11 | 12 | func TestIsPositive_IsTrueFor1(t *testing.T) { 13 | t.Parallel() 14 | input := MyInt(1) 15 | want := true 16 | got := intish.IsPositive(input) 17 | if !cmp.Equal(want, got) { 18 | t.Error(cmp.Diff(want, got)) 19 | } 20 | } 21 | 22 | func TestIsPositive_IsFalseForNegative1(t *testing.T) { 23 | t.Parallel() 24 | input := MyInt(-1) 25 | want := false 26 | got := intish.IsPositive(input) 27 | if !cmp.Equal(want, got) { 28 | t.Error(cmp.Diff(want, got)) 29 | } 30 | } 31 | 32 | func TestIsPositive_IsFalseForZero(t *testing.T) { 33 | t.Parallel() 34 | input := MyInt(0) 35 | want := false 36 | got := intish.IsPositive(input) 37 | if !cmp.Equal(want, got) { 38 | t.Error(cmp.Diff(want, got)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /solutions/3.3/go.mod: -------------------------------------------------------------------------------- 1 | module greater 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.6 6 | -------------------------------------------------------------------------------- /solutions/3.3/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/3.3/greater.go: -------------------------------------------------------------------------------- 1 | package greater 2 | 3 | func IsGreater[T interface{ Greater(T) bool }](x, y T) bool { 4 | return x.Greater(y) 5 | } 6 | -------------------------------------------------------------------------------- /solutions/3.3/greater_test.go: -------------------------------------------------------------------------------- 1 | package greater_test 2 | 3 | import ( 4 | "greater" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | type MyInt int 11 | 12 | func (m MyInt) Greater(v MyInt) bool { 13 | return m > v 14 | } 15 | 16 | func TestIsGreater_IsTrueFor2And1(t *testing.T) { 17 | t.Parallel() 18 | want := true 19 | got := greater.IsGreater(MyInt(2), MyInt(1)) 20 | if !cmp.Equal(want, got) { 21 | t.Error(cmp.Diff(want, got)) 22 | } 23 | } 24 | 25 | func TestIsGreater_IsFalseFor1And2(t *testing.T) { 26 | t.Parallel() 27 | want := false 28 | got := greater.IsGreater(MyInt(1), MyInt(2)) 29 | if !cmp.Equal(want, got) { 30 | t.Error(cmp.Diff(want, got)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solutions/4.1/go.mod: -------------------------------------------------------------------------------- 1 | module product 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/google/go-cmp v0.5.7 7 | golang.org/x/exp v0.0.0-20220205015713-f5f519d967d6 8 | ) 9 | 10 | require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 11 | -------------------------------------------------------------------------------- /solutions/4.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/exp v0.0.0-20220205015713-f5f519d967d6 h1:OoObEk2yqD/yCsmLmK7ZBXY4C5t6FLoHFQH1/+VpMgU= 4 | golang.org/x/exp v0.0.0-20220205015713-f5f519d967d6/go.mod h1:lRnflEfy7nRvpQCcpkwaSP1nkrSyjkyFNcqXKfSXLMc= 5 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 6 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 7 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 8 | -------------------------------------------------------------------------------- /solutions/4.1/product.go: -------------------------------------------------------------------------------- 1 | package product 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | type Number interface { 6 | constraints.Integer | constraints.Float | constraints.Complex 7 | } 8 | 9 | func Product[T Number](x, y T) T { 10 | return x * y 11 | } 12 | -------------------------------------------------------------------------------- /solutions/4.1/product_test.go: -------------------------------------------------------------------------------- 1 | package product_test 2 | 3 | import ( 4 | "product" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | "github.com/google/go-cmp/cmp/cmpopts" 9 | ) 10 | 11 | func TestProductOfInts2And3Is6(t *testing.T) { 12 | t.Parallel() 13 | want := 6 14 | got := product.Product(2, 3) 15 | if !cmp.Equal(want, got) { 16 | t.Error(cmp.Diff(want, got)) 17 | } 18 | } 19 | 20 | func TestProductOfFloats1Point6And2Point3Is3Point68(t *testing.T) { 21 | t.Parallel() 22 | want := 3.68 23 | got := product.Product(1.6, 2.3) 24 | if !cmp.Equal(want, got, cmpopts.EquateApprox(0.0001, 0)) { 25 | t.Error(cmp.Diff(want, got)) 26 | } 27 | } 28 | 29 | func TestProductOfComplex2Plus3iAnd3Plus2iIs0Plus13i(t *testing.T) { 30 | t.Parallel() 31 | want := 0 + 13i 32 | got := product.Product(2+3i, 3+2i) 33 | if !cmp.Equal(want, got) { 34 | t.Error(cmp.Diff(want, got)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /solutions/4.2/dupes.go: -------------------------------------------------------------------------------- 1 | package dupes 2 | 3 | func Dupes[E comparable](s []E) bool { 4 | seen := map[E]bool{} 5 | for _, v := range s { 6 | if seen[v] { 7 | return true 8 | } 9 | seen[v] = true 10 | } 11 | return false 12 | } 13 | -------------------------------------------------------------------------------- /solutions/4.2/dupes_test.go: -------------------------------------------------------------------------------- 1 | package dupes_test 2 | 3 | import ( 4 | "dupes" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestDupesIsTrueWhenInputContainsNonConsecutiveDuplicates(t *testing.T) { 11 | t.Parallel() 12 | s := []int{1, 2, 3, 1, 5} 13 | want := true 14 | got := dupes.Dupes(s) 15 | if !cmp.Equal(want, got) { 16 | t.Error(cmp.Diff(want, got)) 17 | } 18 | } 19 | 20 | func TestDupesIsFalseWhenInputContainsNoDuplicates(t *testing.T) { 21 | t.Parallel() 22 | s := []string{"a"} 23 | want := false 24 | got := dupes.Dupes(s) 25 | if !cmp.Equal(want, got) { 26 | t.Error(cmp.Diff(want, got)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solutions/4.2/go.mod: -------------------------------------------------------------------------------- 1 | module dupes 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /solutions/4.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/5.1/go.mod: -------------------------------------------------------------------------------- 1 | module sequence 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /solutions/5.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/5.1/sequence.go: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | type Sequence[E any] []E 4 | 5 | func (s Sequence[E]) Empty() bool { 6 | return len(s) == 0 7 | } 8 | -------------------------------------------------------------------------------- /solutions/5.1/sequence_test.go: -------------------------------------------------------------------------------- 1 | package sequence_test 2 | 3 | import ( 4 | "sequence" 5 | "testing" 6 | ) 7 | 8 | func TestEmptyIsTrueForEmptySequence(t *testing.T) { 9 | t.Parallel() 10 | s := sequence.Sequence[int]{} 11 | got := s.Empty() 12 | if !got { 13 | t.Fatal("false for empty sequence") 14 | } 15 | } 16 | 17 | func TestEmptyIsFalseForNonEmptySequence(t *testing.T) { 18 | t.Parallel() 19 | s := sequence.Sequence[string]{"a", "b", "c"} 20 | got := s.Empty() 21 | if got { 22 | t.Fatal("true for non-empty sequence") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solutions/6.1/funcmap.go: -------------------------------------------------------------------------------- 1 | package funcmap 2 | 3 | type FuncMap[T, U any] map[string]func(T) U 4 | 5 | func (fm FuncMap[T, U]) Apply(name string, val T) U { 6 | return fm[name](val) 7 | } 8 | -------------------------------------------------------------------------------- /solutions/6.1/funcmap_test.go: -------------------------------------------------------------------------------- 1 | package funcmap_test 2 | 3 | import ( 4 | "funcmap" 5 | "testing" 6 | "unicode" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestFuncMap_AppliesDoubleTo2AndReturns4(t *testing.T) { 12 | t.Parallel() 13 | fm := funcmap.FuncMap[int, int]{ 14 | "double": func(i int) int { 15 | return i * 2 16 | }, 17 | "addOne": func(i int) int { 18 | return i + 1 19 | }, 20 | } 21 | want := 4 22 | got := fm.Apply("double", 2) 23 | if !cmp.Equal(want, got) { 24 | t.Error(cmp.Diff(want, got)) 25 | } 26 | } 27 | 28 | func TestFuncMap_AppliesUpperToUppercaseInputAndReturnsTrue(t *testing.T) { 29 | t.Parallel() 30 | fm := funcmap.FuncMap[rune, bool]{ 31 | "upper": unicode.IsUpper, 32 | "lower": unicode.IsLower, 33 | } 34 | got := fm.Apply("upper", 'A') 35 | if !got { 36 | t.Fatal("upper('A'): want true, got false") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /solutions/6.1/go.mod: -------------------------------------------------------------------------------- 1 | module funcmap 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /solutions/6.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/6.2/compose.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | func Compose[T, U, V any](f func(U) T, g func(V) U, v V) T { 4 | return f(g(v)) 5 | } 6 | -------------------------------------------------------------------------------- /solutions/6.2/compose_test.go: -------------------------------------------------------------------------------- 1 | package compose_test 2 | 3 | import ( 4 | "compose" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func isOdd(p int) bool { 12 | return p%2 != 0 13 | } 14 | 15 | func next(p int) int { 16 | return p + 1 17 | } 18 | 19 | func TestComposeAppliesFuncsToIntInReverseOrder(t *testing.T) { 20 | t.Parallel() 21 | odd := compose.Compose(isOdd, next, 1) 22 | if odd { 23 | t.Fatal("isOdd(next(1)): want false, got true") 24 | } 25 | } 26 | 27 | func TestComposeAppliesFuncsToStringInReverseOrder(t *testing.T) { 28 | t.Parallel() 29 | input := "HeLlO, wOrLd" 30 | want := "hello, world" 31 | got := compose.Compose(strings.ToLower, strings.ToUpper, input) 32 | if !cmp.Equal(want, got) { 33 | t.Error(cmp.Diff(want, got)) 34 | } 35 | } 36 | 37 | func first[E any](s []E) E { 38 | return s[0] 39 | } 40 | 41 | func last[E any](s []E) E { 42 | return s[len(s)-1] 43 | } 44 | 45 | func TestComposeAppliesFuncsToSliceInReverseOrder(t *testing.T) { 46 | t.Parallel() 47 | input := [][]int{{1, 2, 3}} 48 | want := 1 49 | got := compose.Compose(first[int], last[[]int], input) 50 | if !cmp.Equal(want, got) { 51 | t.Error(cmp.Diff(want, got)) 52 | } 53 | } 54 | 55 | func TestComposeAppliesFuncsToSliceInReverseOrder2(t *testing.T) { 56 | t.Parallel() 57 | input := [][]int{{1, 2, 3}} 58 | want := 3 59 | got := compose.Compose(last[int], first[[]int], input) 60 | if !cmp.Equal(want, got) { 61 | t.Error(cmp.Diff(want, got)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /solutions/6.2/go.mod: -------------------------------------------------------------------------------- 1 | module compose 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /solutions/6.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/7.1/go.mod: -------------------------------------------------------------------------------- 1 | module stack 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /solutions/7.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/7.1/stack.go: -------------------------------------------------------------------------------- 1 | package stack 2 | 3 | type Stack[E any] struct { 4 | data []E 5 | } 6 | 7 | func (s Stack[E]) Len() int { 8 | return len(s.data) 9 | } 10 | 11 | func (s *Stack[E]) Push(vals ...E) { 12 | s.data = append(s.data, vals...) 13 | } 14 | 15 | func (s *Stack[E]) Pop() (v E, ok bool) { 16 | if len(s.data) == 0 { 17 | return v, false 18 | } 19 | v = s.data[len(s.data)-1] 20 | s.data = s.data[:len(s.data)-1] 21 | return v, true 22 | } 23 | -------------------------------------------------------------------------------- /solutions/7.1/stack_test.go: -------------------------------------------------------------------------------- 1 | package stack_test 2 | 3 | import ( 4 | "stack" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestPushOneValueToEmptyStackLeavesTheStackWithLength1(t *testing.T) { 11 | t.Parallel() 12 | s := stack.Stack[int]{} 13 | s.Push(0) 14 | if s.Len() != 1 { 15 | t.Fatal("Push didn't add value to stack") 16 | } 17 | } 18 | 19 | func TestPushTwiceThenPopTwiceOnEmptyStackLeavesTheStackEmpty(t *testing.T) { 20 | t.Parallel() 21 | s := stack.Stack[string]{} 22 | s.Push("a", "b") 23 | if s.Len() != 2 { 24 | t.Fatal("Push didn't add all values to stack") 25 | } 26 | s.Pop() 27 | want := "a" 28 | got, ok := s.Pop() 29 | if !ok { 30 | t.Fatal("Pop returned not ok on non-empty stack") 31 | } 32 | if !cmp.Equal(want, got) { 33 | t.Error(cmp.Diff(want, got)) 34 | } 35 | } 36 | 37 | func TestPopReturnsNotOKOnEmptyStack(t *testing.T) { 38 | t.Parallel() 39 | s := stack.Stack[int]{} 40 | _, ok := s.Pop() 41 | if ok { 42 | t.Fatal("Pop returned ok on empty stack") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /solutions/8.1/billable.go: -------------------------------------------------------------------------------- 1 | package billable 2 | 3 | import "sync" 4 | 5 | type Channel[T any] struct { 6 | lock *sync.RWMutex 7 | ch chan T 8 | sends, receives int 9 | } 10 | 11 | func NewChannel[T any](length int) *Channel[T] { 12 | return &Channel[T]{ 13 | lock: &sync.RWMutex{}, 14 | ch: make(chan T, length), 15 | } 16 | } 17 | 18 | func (c *Channel[T]) Send(v T) { 19 | c.ch <- v 20 | c.lock.Lock() 21 | defer c.lock.Unlock() 22 | c.sends++ 23 | } 24 | 25 | func (c *Channel[T]) Receive() T { 26 | v := <-c.ch 27 | c.lock.Lock() 28 | defer c.lock.Unlock() 29 | c.receives++ 30 | return v 31 | } 32 | 33 | func (c *Channel[T]) Sends() int { 34 | c.lock.RLock() 35 | defer c.lock.RUnlock() 36 | return c.sends 37 | } 38 | 39 | func (c *Channel[T]) Receives() int { 40 | c.lock.RLock() 41 | defer c.lock.RUnlock() 42 | return c.receives 43 | } 44 | -------------------------------------------------------------------------------- /solutions/8.1/billable_test.go: -------------------------------------------------------------------------------- 1 | package billable_test 2 | 3 | import ( 4 | "billable" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestSendFollowedByReceiveGivesOriginalValue(t *testing.T) { 12 | t.Parallel() 13 | c := billable.NewChannel[int](1) 14 | want := 99 15 | c.Send(want) 16 | got := c.Receive() 17 | if !cmp.Equal(want, got) { 18 | t.Fatal(cmp.Diff(want, got)) 19 | } 20 | } 21 | 22 | func TestSendsReturns3AfterThreeSendOperations(t *testing.T) { 23 | t.Parallel() 24 | c := billable.NewChannel[float64](3) 25 | c.Send(1.0) 26 | c.Send(2.0) 27 | c.Send(3.0) 28 | want := 3 29 | got := c.Sends() 30 | if want != got { 31 | t.Fatalf("want %d sends, got %d", want, got) 32 | } 33 | } 34 | 35 | func TestReceivesReturns2AfterTwoSendOperations(t *testing.T) { 36 | t.Parallel() 37 | c := billable.NewChannel[struct{}](1) 38 | c.Send(struct{}{}) 39 | _ = c.Receive() 40 | c.Send(struct{}{}) 41 | _ = c.Receive() 42 | want := 2 43 | got := c.Receives() 44 | if want != got { 45 | t.Fatalf("want 2 receives, got %d", got) 46 | } 47 | } 48 | 49 | func TestChannelDoesNotPanicOnManyConcurrentSendsAndReceives(t *testing.T) { 50 | t.Parallel() 51 | c := billable.NewChannel[string](10) 52 | want := 100 53 | var wg sync.WaitGroup 54 | wg.Add(1) 55 | go func() { 56 | for i := 0; i < want; i++ { 57 | c.Send("hello") 58 | _ = c.Receives() 59 | } 60 | wg.Done() 61 | }() 62 | for i := 0; i < want; i++ { 63 | _ = c.Receive() 64 | _ = c.Sends() 65 | } 66 | wg.Wait() 67 | got := c.Sends() 68 | if want != got { 69 | t.Errorf("want %d sends, got %d", want, got) 70 | } 71 | got = c.Receives() 72 | if want != got { 73 | t.Errorf("want %d receives, got %d", want, got) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /solutions/8.1/go.mod: -------------------------------------------------------------------------------- 1 | module billable 2 | 3 | go 1.18 4 | 5 | require github.com/google/go-cmp v0.5.7 6 | -------------------------------------------------------------------------------- /solutions/8.1/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 2 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 3 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 4 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 5 | -------------------------------------------------------------------------------- /solutions/9.1/contains.go: -------------------------------------------------------------------------------- 1 | package contains 2 | 3 | import "golang.org/x/exp/slices" 4 | 5 | func ContainsFunc[E any](s []E, f func(E) bool) bool { 6 | return slices.IndexFunc(s, f) >= 0 7 | } 8 | -------------------------------------------------------------------------------- /solutions/9.1/contains_test.go: -------------------------------------------------------------------------------- 1 | package contains_test 2 | 3 | import ( 4 | "contains" 5 | "testing" 6 | "unicode" 7 | ) 8 | 9 | func positive(p int) bool { 10 | return p > 0 11 | } 12 | 13 | func TestContainsFunc_IsTrueForPositiveOnInputContainingPositiveInts(t *testing.T) { 14 | t.Parallel() 15 | input := []int{-2, 0, 1, -1, 5} 16 | got := contains.ContainsFunc(input, positive) 17 | if !got { 18 | t.Fatalf("%v: want true for 'contains positive', got false", input) 19 | } 20 | } 21 | 22 | func TestContainsFunc_IsFalseForIsUpperOnInputContainingNoUppercaseRunes(t *testing.T) { 23 | t.Parallel() 24 | input := []rune("hello") 25 | got := contains.ContainsFunc(input, unicode.IsUpper) 26 | if got { 27 | t.Fatalf("%q: want false for 'contains uppercase', got true", input) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solutions/9.1/go.mod: -------------------------------------------------------------------------------- 1 | module contains 2 | 3 | go 1.18 4 | 5 | require golang.org/x/exp v0.0.0-20220217172124-1812c5b45e43 6 | -------------------------------------------------------------------------------- /solutions/9.1/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f h1:u4dL7EmDaaJ+1e0HD9rawKa15yKPQZWXQ/epCOPAU+A= 2 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f/go.mod h1:M50CtfS+xv2iy/epuEazynj250ScQ0/DOjcsin9UE8k= 3 | golang.org/x/exp v0.0.0-20220217172124-1812c5b45e43 h1:Xo03zeNci09uW1tocp7+8X7YizAdkD/BKNkl9lsqKHQ= 4 | golang.org/x/exp v0.0.0-20220217172124-1812c5b45e43/go.mod h1:lRnflEfy7nRvpQCcpkwaSP1nkrSyjkyFNcqXKfSXLMc= 5 | -------------------------------------------------------------------------------- /solutions/9.2/go.mod: -------------------------------------------------------------------------------- 1 | module merge 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/google/go-cmp v0.5.6 7 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f 8 | ) 9 | -------------------------------------------------------------------------------- /solutions/9.2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 2 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f h1:u4dL7EmDaaJ+1e0HD9rawKa15yKPQZWXQ/epCOPAU+A= 4 | golang.org/x/exp v0.0.0-20220121174013-7b334a16533f/go.mod h1:M50CtfS+xv2iy/epuEazynj250ScQ0/DOjcsin9UE8k= 5 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 6 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 7 | -------------------------------------------------------------------------------- /solutions/9.2/merge.go: -------------------------------------------------------------------------------- 1 | package merge 2 | 3 | import "golang.org/x/exp/maps" 4 | 5 | func Merge[M ~map[K]V, K comparable, V any](ms ...M) M { 6 | result := M{} 7 | for _, m := range ms { 8 | maps.Copy[map[K]V](result, m) 9 | } 10 | return result 11 | } 12 | -------------------------------------------------------------------------------- /solutions/9.2/merge_test.go: -------------------------------------------------------------------------------- 1 | package merge_test 2 | 3 | import ( 4 | "merge" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestMergeCorrectlyMergesTwoMapsOfIntToBool(t *testing.T) { 11 | t.Parallel() 12 | inputs := []map[int]bool{ 13 | { 14 | 1: false, 15 | 2: false, 16 | 3: false, 17 | }, 18 | { 19 | 3: true, 20 | 5: true, 21 | }, 22 | } 23 | want := map[int]bool{ 24 | 1: false, 25 | 2: false, 26 | 3: true, 27 | 5: true, 28 | } 29 | got := merge.Merge(inputs...) 30 | if !cmp.Equal(want, got) { 31 | t.Error(cmp.Diff(want, got)) 32 | } 33 | } 34 | 35 | func TestMergeCorrectlyMergesThreeMapsOfStringToAny(t *testing.T) { 36 | t.Parallel() 37 | inputs := []map[string]any{ 38 | { 39 | "a": nil, 40 | }, 41 | { 42 | "b": "hello, world", 43 | "c": 0, 44 | }, 45 | { 46 | "a": 6 + 2i, 47 | }, 48 | } 49 | want := map[string]any{ 50 | "a": 6 + 2i, 51 | "b": "hello, world", 52 | "c": 0, 53 | } 54 | got := merge.Merge(inputs...) 55 | if !cmp.Equal(want, got) { 56 | t.Error(cmp.Diff(want, got)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /solutions/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for file in ./*; do 4 | if [ -d "$file" ] ; then 5 | echo Testing $file... 6 | cd $file 7 | go test 8 | cd .. 9 | fi 10 | done 11 | --------------------------------------------------------------------------------