Let's solve Advent of Code 2024 Day 22!

AOC 2024, Day 22: Maximum Bananas


Advent of Code Day 22 was quite an easy entry this late in the year. The only thing presented the slightest challenge was understanding the problem statement. Let’s figure it out!


The below is a guest post by the amazing Shraddha Agrawal.


Part 1

Input is the list of secret numbers of each buyer in the money market. Each secret number is rotated by following a set of rules:

  1. Find value of secret number by multiplying it with 64. This value should be mixed and pruned.
  2. The secret number generated in step 1 should now be divided by 32 to get the nearest integer. This value should be mixed and pruned.
  3. The secret number generated in step 2 should now be multiplied with 2048. This value should be mixed and pruned.

The above mentions mixing and pruning, explained below:

  • Mixing - The secret number is now the bitwise XOR of the given value and the secret number.
  • Pruning - The secret number is now the its modulo with 16777216.

For each input secret number, we need to find the secret number after rotating it 2000 times. The final answer should be the sum of all secret numbers.

Solution

This is a fairly simple problem, just need to implement the rules as given. Let’s first implement the mixing and pruning rules:

func mix(value, secretNum int64) int64 {
	return value ^ secretNum
}

func prune(value int64) int64 {
	return value % 16777216
}

Now, lets write the function to rotate a secret number following the rules specified above:

func findSecretNumber(input int64) int64 {
	input = prune(mix(int64(64)*input, input))
	input = prune(mix(input/32, input))
	input = prune(mix(input*2048, input))
	return input
}

We just need to loop 2000 times to generate secret numbers now:

func calculateSecretNums(input []string) int {
	var sum int64

	for i, line := range input {
		num := int64(aoc.FetchNumFromStringIgnoringNonNumeric(line))

		for j := range 2000 {
			num = findSecretNumber(num)
		}
		
		sum += num
	}

	return sum
}

This gives the answer for part 1.

Part 2

The number of bananas we get with each secret number is actually the last digit of the secret number.

The monkey who is actually doing the transaction on our behalf, only keeps tracks of the change in number of bananas as the secret numbers are rotated. It can at most keep track of 4 consecutive changes. We need to tell the monkey the sequence of 4 changes, which when first encountered, the monkey should trade it for bananas, for each input secret code.

We need to find the sequence of changes that will get the maximum number of bananas. The answer is the maximum count of bananas we can get.

Solution

First, for each new secret number we generate, we need to store the number of bananas and the change from the previous number of bananas.

I created a new type to store the number of bananas and the change:

type bananaPrice struct {
	num    int
	change int
}

Now, we can calculate the above while we are generating secret numbers in part 1 for each input secret number and all its rotations.

func calculateSecretNums(input []string) (int64, int) {
	var sum int64
	b := make([][]bananaPrice, len(input))
	
	for i, line := range input {
		b[i] = make([]bananaPrice, 2000)
		num := int64(aoc.FetchNumFromStringIgnoringNonNumeric(line))
		prev := int(num % 10)
		
		for j := range 2000 {
			num = findSecretNumber(num)
			b[i][j] = getNumAndChange(num, prev)
			prev = int(num % 10)
		}
		
		sum += num
	}

	return sum, findMaxNumberOfBananas(b)
}

Since we need to give the money a sequence of 4 changes, we now need to identify what are the possible unique sequence of 4 changes and how many bananas we get for each input secret code when this sequence is first encountered. Its very important to find the number of bananas we get on the first occurrence of the sequence, I misread this part and wasted quite a lot of time trying to figure out what I might be doing wrong.

I stored this information using a map. The key is the sequence of 4 changes and the value is a slice with length equal to count of input secret codes. When each unique sequence is first encountered in an input secret code, we will store the number bananas in the value slice.

Once we have this map, then its just a matter to sum up the number of bananas in each value slice and the maximum count is the answer for part 2.

func findMaxNumberOfBananas(b [][]bananaPrice) int {
	seqMap := make(map[seq][]int)
	for i, _ := range b {
		s := []int{b[i][0].change, b[i][1].change, b[i][2].change}
		for j := 3; j < len(b[i]); j++ {
			s = append(s, b[i][j].change)

			if _, ok := seqMap[seq{s[0], s[1], s[2], s[3]}]; !ok {
				seqMap[seq{s[0], s[1], s[2], s[3]}] = make([]int, len(b))
			}

			if seqMap[seq{s[0], s[1], s[2], s[3]}][i] == 0 {
				seqMap[seq{s[0], s[1], s[2], s[3]}][i] = b[i][j].num
			}

			s = s[1:]
		}
	}
	max := 0
	for _, n := range seqMap {
		sum := 0
		for _, r := range n {
			sum += r
		}
		if sum > max {
			max = sum
		}
	}
	return max
}

And thats it! My final code for this day can be found here.

This day was quite easy but the way it was worded took me minute to understand. Once I realised we have to find the number of bananas only for the first occurrence of the sequence, it was a straightforward solution. A lot of people also got stuck because the test input for part 2 changes from part 1 and it wasn’t immediately obvious. Advent of Code this year had a pattern where solving the problem themselves were quite easy but comprehending what is the problem is the actual hard part.

How did you solve Day 22? I would love to compare notes and checkout your solution. Reach out to me on TwitterBluesky or on e-mail at contact[at]shraddhaag[dot]dev.