Advent of Code Day22: Monkey Market

After spending far too long yesterday unsuccessfully trying to get my head around nested recursions, today (1)https://adventofcode.com/2024/day/22 was something a little easier.

Part 1 #

We are given a list of numbers and a transformation to apply to them.

It’s a simple enough process and careful reading let’s it be written directly.

func mix(_ number: Int, _ secretNumber: Int) -> Int {
  number ^ secretNumber
}

func prune(_ secretNumber: Int) -> Int {
  secretNumber % 16777216
}

func next(from: Int) -> Int {
  let a = prune(mix(from * 64, from))
  let b = prune(mix(Int(floor(Double(a) / 32.0)), a))
  let c = prune(mix(b * 2048, b))

  return c
}

I wrote functions for the mix and prune transformation, and a next function to generate a new number from the old one.

Then I wrote a function that returns a function that takes a number and transforms it the given number of times. I find it easier to write function returning functions when they are used with map operations.

func evolvutionFunction(n: Int) -> (Int) -> Int {
  { secretNumber in
    var num = secretNumber
    for _ in 0 ..< n {
      num = next(from: num)
    }

    return num
  }
}

And then put it all together.

func part1() async throws -> Int {
  rows.map(evolvutionFunction(n: 2000)).reduce(0, +)
}

As I said, nothing fancy, just write code to match the requirements.

Part 2 #

There are further transformations to make and then a calculation to make.

func evolutionReduction(n: Int) -> (Int) -> [Int] {
  { secretNumber in
    (0 ..< n).reductions(secretNumber) { num, _ in
      next(from: num)
    }
  }
}

Rather than return a single number, this returns a list of all the secret numbers generated. I used the reductions (2)https://swiftpackageindex.com/apple/swift-algorithms/1.2.0/documentation/algorithms/reductions method from the Swift-Algorithms package.

The main processing is done in the differences method

func differences(_ numbers: [Int]) -> [String: Int] {
   let ones = numbers.map { $0 % 10 }
   var dict: [String: Int] = [:]

   for slice in ones.windows(ofCount: 5) {
     var k: [String] = []
     let v = slice.last!
     for (n, l) in zip(slice.dropFirst(), slice) {
       k.append(String(n - l))
     }
     let key = k.joined(separator: ",")
     guard dict[key] == nil else { continue }
     dict[key] = v
   }
   return dict
 }

I used the modulo method to get the ones digit. And then I used the windows(ofCount:) to get overlapping slices of length 5 (I need five number to get 4 differences) and I make a string out of the list of differences (Swift tuples are not hashable so I can’t use them as dictionary keys). I only add a sequence the first time it is found, that is one of the requirements of the puzzle.

I now have a dictionary of the a sequence of 4 changes, and the number of bananas that will be gained if that sequence is chosen.

func part2() async throws -> Int {
  rows
    .map(evolutionReduction(n: 2000))
    .map(differences)
    .reduce(into: [String: Int]()) { partialResult, dict in
      for key in dict.keys {
        partialResult[key, default: 0] += dict[key] ?? 0
      }
    }
    .values.max() ?? 0
}

I take the dictionary for each of the numbers and then join them together with a reduction. Adding up the values of the bananas if the sequence already exists in the partial result (the accumulator).

The result is just the largest value in the dictionary.

Final Thoughts #

The full code is on Github (3)https://github.com/Abizern/aoc-swift-2024/blob/main/Sources/Day22.swift .

Unfortunately, I wasted a lot of time trying to debug my code before I realised that the example input for part 2 is not the same as the input for Part 1.

Both parts run in under 2s and I can’t see any obvious way of making this run any faster. Also, unlike the beginning of the month, I don’t feel the urge to make my code as elegant as possible. It works, I get results in a reasonable amount of time, and I’ve written notes about my work on here.

Just a few more days to go. Maybe just one more for me before I finish the others off between Christmas and the New Year.

>