Day 5: A Maze of Twisty Trampolines, All Alike

Notice: If you have coding experience, I highly recommend to try and solve the puzzle in a language you already know. It's a lot of fun and you'll be able to focus on Go more later on.

Ho ho ho, welcome back for another puzzle! You know the drill by know: if you haven’t done so yet, go and read the problem description. Go on, I’ll wait here until you’re ready.

Good, you’re back! Let’s get started. On the agenda for today: Testing your code.

Sooooo, this is kinda awkward. Todays puzzle is so easy, that I don’t have any new concepts to introduce in the regular code! Look at it, it’s so nice and clean and simple and… boring. Just do what the puzzle tells you to do: jump from one index in a slice to another, until you reach an index that’s outside the slice. BORING!

func readFile(fname string) []int {
	file, err := os.Open(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	lines := []int{}

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		a, err := strconv.Atoi(scanner.Text())
		if err != nil {
			panic(err)
		}
		lines = append(lines, a)
	}
	return lines
}

func step(jumps []int, partb bool) int {
	var steps, ip, oldip int

	for ip >= 0 && ip < len(jumps) {
		oldip = ip

		ip += jumps[ip]

		if partb && jumps[oldip] >= 3 {
			jumps[oldip]--
		} else {
			jumps[oldip]++
		}

		steps++
	}
	return steps
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Expected the input file name as commandline argument.")
		os.Exit(1)
	}

	ints := readFile(os.Args[1])

	jumps := make([]int, len(ints))

	copy(jumps, ints)
	fmt.Println("Part A", step(jumps, false))

	copy(jumps, ints)
	fmt.Println("Part B", step(jumps, true))
}

If I do have to point you to one little gotcha, it’s the builtin copy function. You have to use it to make a deep copy of the input slice to make sure you’re starting part B with the original input. But that’s it. So let’s talk about something else. Until now, we’ve been kinda freewheeling through the puzzles. I acted like the algorithm was clear cut and like I’m so smart that all the solutions were correct on first submission. They weren’t. And I’m not.

Each of the puzzles comes with a simple test case that you can use to verify your own solution. Go has a builtin test framework to help you with that. The go test command will run all the tests in files with a name ending in _test.go. You can name the file whatever you want, but it’s best practice to put the tests for code from day05.go in a file called day05_test.go in the same package.

So what does a test look like? A test is a function that has a name prefixed with “Test” and that takes a *testing.T variable as argument. The T argument is passed in by the testing framework and is used to communicate with it. One of its most basic functions is signalling wether or not a test failed and it should be terminated.

Basic test functions for the step function from our solution above, may look like this:

package day05

func TestStep_partA(t *testing.T) {
	r := step([]int{0, 3, 0, 1, -3}, false)
	if r != 5 {
		t.Errorf("Error: expected 5 steps, got %d",
			f.Result, r)
	}
}

func TestStep_partB(t *testing.T) {
	r := step([]int{0, 3, 0, 1, -3}, true)
	if r != 10 {
		t.Errorf("Error: expected 10 steps, got %d",
			f.Result, r)
	}
}

You can pass the package name as CLI argument to the go test command.

thomas@local:~$ go test github.com/blackskad/adventofcode/2017/day05
ok  	github.com/blackskad/adventofcode/2017/day05	0.008s

The cool thing is that the test framework comes with a whole bunch of more advanced features. One of the simpler ones is code coverage. If you provide the -coverprofile argument, you’ll get a test coverage overview that can be rendered as an html page. It may not be super useful for the code you write for the Advent of Code puzzles, but it’s really nice for larger codebases.

thomas@local:~$ go test -coverprofile=coverage.out github.com/blackskad/adventofcode/2017/day05
ok  	github.com/blackskad/adventofcode/2017/day05	0.009s	coverage: 30.0% of statements
thomas@local:~$ go tool cover -html=coverage.out
thomas@local:~$ 

Alright folks, that’s it for today. Go out,… and test your code. As usual, you can find my complete solution in my github repository. See you all tomorrow for the next puzzle!