Advent of Code - Day 4

Disclaimer: There is surely a better solution than this one. But I’m fairly okay with this one. And I wrote it, so…

Okay, this day was a bit more complex now. But not in understanding what needed to be done, but implementing it. And then, part two of course.

Day 4 - Part 1

We met a squid this day and decided to play a round of Bingo with it. Because, why not? Bingo is a simple game. You have several papers on which there are a couple numbers in a square. Someone draws a number, and you need to find that number on your boards, cards, slips, whatevers. Once you have marked 5 numbers in a row, or a column ( or, in the original game diagonal also goes ) you win.

Easy. You have a matrix. Get a number. Mark the numbers in the matrix which equal to the drawn number.

I’m gonna follow this logic. There might be better ones where you can calculate things based on some math, I encourage you to look at different solutions as well.

First, we parse the input. Get the first line, and then skip the first two so we can get to the numbers.

	for i, l := range split {
		if i < 2 {
			continue
		}
		if l == "" {
			boards = append(boards, board)
			board = make([][]int, 0)
			continue
		}

		nums := strings.Fields(l)
		n := make([]int, 0)
		for _, number := range nums {
			i, err := strconv.Atoi(number)
			if err != nil {
				log.Fatal(err)
			}
			n = append(n, i)
		}
		board = append(board, n)
	}

This should do it. Now, there is a small surprise here. The numbers in the matrix are not separated by just a single space. They are separated by multiple ones. This is just a small extra to mess with your head. Lucky for us, Go has us covered with strings.Fields. This will retrieve just the numbers without the spaces.

Now, we get to the main logic… Draw, mark, see if we have a winning board, calculate score.

	for _, n := range nums {
		markBoards(n, boards)
		if winner, ok := hasWinner(boards); ok {
			fmt.Println("winner score: ", calculateScore(n, winner))
			fmt.Println("winner board: ", winner)
			os.Exit(0)
		}
	}

Pretty easy. Marking the board is just going through all the boards in the list of boards and do a for i, for j to get at the right number and change that number to -1. Why does -1 work? Because if we read on, the score is calculated by going through the board and adding up UNMARKED numbers. So we don’t need any special way to keep track of marked numbers. We just care about the unmarked ones.

Now, hasWinner is a bit more complex, but just because we need to check horizontally and vertically. And we have to remember that we have to stop immediately once we found a winner.

I’m sure there is a better way to do this, but I just went for two loops:

func hasWinner(boards [][][]int) ([][]int, bool) {
	for _, board := range boards {
		for i := 0; i < len(board); i++ {
			// check vertically
			rowWon := true
			for j := 0; j < len(board[i]); j++ {
				if board[i][j] != -1 {
					rowWon = false
					break
				}
			}
			if rowWon {
				return board, true
			}
		}

		// these are 5x5 and not varring in size.
		l := len(board[0])
		for col := 0; col < l; col++ {
			colWon := true
			for i := 0; i < len(board); i++ {
				if board[i][col] != -1 {
					colWon = false
					break
				}
			}
			if colWon {
				return board, true
			}
		}
	}

	return nil, false
}

And then the calculation is just again going through and doing the adding and then the multiplication.

We run it on our test data and input and it works. Fantastic. On to part 2!

Day 4 - Part 2

Part 2 is just about finding the last winning board. Great! We are already geared towards that. Once we find a winning board we remove that board and begin again. We do this until there are no boards left. I’m not sure how that will make sure that the squid wins, I don’t understand that part to be honest, but that’s not our goal.

For this, we slightly alter our main loop like this:

	for len(boards) > 0 {
		for _, n := range nums {
			markBoards(n, boards)
			if loc, winner, ok := hasWinner(boards); ok {
				fmt.Printf("winner score: number: %d, score: %d, loc: %d\n", n, calculateScore(n, winner), loc)
				boards = append(boards[:loc], boards[loc+1:]...)
				break
			}
		}
	}

This will display all boards which win. I don’t really care about that, again, I could have just checked the last, or save all or when len==1 check it. But this was the fastest, easiest way to do it. And as I said in the begin, these are small scripts which I will probably never look at again, so I don’t care about maintainability. I care that it’s efficient, fast and easy to read / write.

We run this, and it works on the test and the input. And that’s it for part 2!

Conclusion

We learned again that reading things and understanding can be hard. The samples can be daunting, but don’t let yourself be intimidated. And never be afraid to ask questions, look at what other people are doing especially if you just started doing AOC this year.

But most importantly, have fun with it!

The repository for all my solutions for AOC 2021 can be found here.

Thank you for reading!

Gergely.