If you're stuck look at examples on Go by Example
Before starting this lab, make sure that your machine has Go correctly installed and your favourite editor/IDE properly configured. Go is supported by Linux, macOS and Windows.
If you're using the 2.11 lab machines we wrote a quick setup guide for configuring Go and VS Code.
You can find all setup guides here.
There are two ways to use the lab sheet, you can either:
- create a new repo from this template - this is the recommended way
- download a zip file
Each question is rated to help you balance your work:
- 🔴⚪⚪⚪⚪ - Easy, strictly necessary.
- 🔴🔴⚪⚪⚪ - Medium, still necessary.
- 🔴🔴🔴⚪⚪ - Hard, necessary if you're aiming for higher marks.
- 🔴🔴🔴🔴⚪ - Hard, useful for coursework extensions.
- 🔴🔴🔴🔴🔴 - Hard, beyond what you need for any part of the coursework.
Below is a complete 'Hello World' program written in Go:
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
Type the above program into a new file hello.go
(don't just copy and paste). To run your code you can either use go run hello.go
or go build hello.go
followed by ./hello
. Verify that Hello World is printed in both cases.
Modify hello.go
so that it uses a for loop to print Hello World 20 times.
Open quiz.go
. It's a skeleton for a quiz program. Write a main()
using the provided helper functions so that your program asks the 6 questions from quiz-questions.csv
and prints out the final score at the end.
Hint 1
Use s := score(0)
to initialise your score variable.
Hint 2
Use a for-range loop to ask all the questions.
Open sequences.go
.
Implement mapSlice
and mapArray
using for-range loops.
They are the same as Haskell's map. For example mapping addOne
onto [5, 10, 15]
should return [6, 11, 16]
.
In main()
:
-
Create a slice
intsSlice
with values[1, 2, 3]
. -
Map
addOne
onto this slice. -
Print
intsSlice
. -
Create an array
intsArray
of length 3 with values[1, 2, 3]
. -
Map
addOne
onto this array. -
Print
intsArray
.
Explain the result. Modify the skeleton to solve any issues that you may have observed.
Hint
How are slices different from arrays? What exactly are they?
Change the definitions of intsArray
and intsSlice
so that they both contain values [1, 2, 3, 4, 5]
. Without modifying mapSlice
or mapArray
run your new program. Explain the result.
Slices support a “slice” operator with the syntax slice[lowIndex:highIndex]
. It allows you to cut out a portion of your slice. For example:
// Given: intsSlice = [2, 3, 4, 5, 6]
newSlice := intsSlice[1:3]
// newSlice = [3, 4]
Define newSlice
as shown above, map square
onto newSlice
and print both newSlice
and the original intSlice
. Explain the result.
The function double
should append a slice onto itself. For example, given [5, 6, 7]
it should return [5, 6, 7, 5, 6, 7]
. In main
, try doubling and then printing your intsSlice
. Explain the result. Modify the skeleton to solve any issues that you may have observed.
Given the differences that you found between arrays and slices:
- Explain how arrays and slices are passed to functions.
- Explain how
append()
works. - Give use cases for arrays and slices.
Time to test what you've learnt!
Open gol.go
. This is a skeleton for a program that can run a serial game of life simulation.
The British mathematician John Horton Conway devised a cellular automaton named ‘The Game of Life’. The game resides on a 2-valued 2D matrix, i.e. a binary image, where the cells can either be ‘alive’ (pixel value 255 - white) or ‘dead’ (pixel value 0 - black). The game evolution is determined by its initial state and requires no further input. Every cell interacts with its eight neighbour pixels: cells that are horizontally, vertically, or diagonally adjacent. At each matrix update in time the following transitions may occur to create the next evolution of the domain:
- any live cell with fewer than two live neighbours dies
- any live cell with two or three live neighbours is unaffected
- any live cell with more than three live neighbours dies
- any dead cell with exactly three live neighbours becomes alive
Consider the image to be on a closed domain (pixels on the top row are connected to pixels at the bottom row, pixels on the right are connected to pixels on the left and vice versa). A user can only interact with the Game of Life by creating an initial configuration and observing how it evolves. Note that evolving such complex, deterministic systems is an important application of scientific computing, often making use of parallel architectures and concurrent programs running on large computing farms.
Complete the calculateNextState(p golParams, world [][]byte) [][]byte
function. This function takes the current state of the world and completes one evolution of the world. It then returns the result.
Complete the calculateAliveCells(p golParams, world [][] byte) []cell
function. This function takes the world as input and returns the (x, y) coordinates of all the cells that are alive.
Make sure that all your tests pass! (go test .
)
Once all your tests pass you can use go run .
and see if you get the hidden message when you open output.pgm
.
A goroutine is a lightweight thread of execution. Modify your hello.go
so that it uses a for loop to start 5 goroutines and print Hello from goroutine i
where i
is the number of the goroutine.
Example output:
$ go run hello.go
Hello from goroutine 2
Hello from goroutine 3
Hello from goroutine 4
Hello from goroutine 0
Hello from goroutine 1
Hint 1 - How do I start a new goroutine?
Starting a goroutine is easy, just say:
go someFunc()
Hint 2 - Why does my program exit without printing anything?
You may notice that your program exits without printing anything. For now you can fix this by placing this after your for loop:
time.Sleep(1 * time.Second)
Soon you'll see how to fix this problem with channels.