Advent of Code 2025

Advent of Code (AoC) for this year started yesterday. I’ve been too busy with work to write here how my puzzle solving has been going. Or anything else, for that matter. Probably will be for the rest of this year.

Why busy? I’ve been moving Data structures and algorithms course exams from Moodle to Exam. Exam is a controlled environment as Moodle was not (at all). There is a tool to import Moodle exam questions to Exam but that is so limited that I decided to hand write new exam questions for this year.

This has taken time, also learning how the Exam works since I have never used it before. Also I’ve been preparing new demonstrations and visualizations for the course to aid learning. There’s still one visualization demonstration to do, namely how the Dijkstra’s shortest path algorithm works. If I do not find the time for that, I just have to search for existing demonstrations from the internet. Would like to use the familiar graph examples from the lectures and other demo materials though.

Anyhows, I have solved both Day 1 and Day 2 puzzles in AoC, using Swift as last year. If you wish to see how the actual puzzles look like, just head on to the AoC website.

Yesterday’s part 2 was a bit difficult, mainly due to some basic mistakes I made in the beginning, and not starting from scratch when I saw the mistakes I had “fixed” with unnecessarily complex code. Sauna break in the evening helped, teaching and other work in between less so 😅. And starting from scratch was a good idea.

Day 1 implementations were fast, part 1 took 0.000684708 seconds to execute, part 2 took 0.000698 seconds (on MacBook Pro M2, Swift implementation, release build, obviously). Lines of code I needed for the part 1 solution is 18, for part 2 line count is 32.

Day 2 implementation’s Part 1 (17 lines of code) took 0.150482667 seconds to execute, while part 2 (27 lines of code) took 0.437480584 seconds. Here I was using Swift’s .split, .map, stride (a for loop kind of a thing) and chunks(ofCount:) from Swift Algorithms package. Much more straightforward (for me) than yesterday’s part 2.

Now to lunch, then to prepare for a MSc thesis supervision session, then to teach (remotely, as usual) for the rest of the afternoon.

Slowly out of Corona virus

I got the corona virus again, the second time for me. Previous infection was in August 2022, and then it was really bad. I had a terrible headache and very, very sore throat for two weeks. Couldn’t event swallow saliva without much pain, even though I ate the max amount of over-the-counter pain killers.

This time was not as bad but still quite bad. Partly the same symptoms but now it was more like a really bad flu with almost as bad headache and sore throat as last time. It’s almost three weeks since I got the first symptoms and it is still not over.

Anyways, I did have the energy to solve Advent of Code days 10 and 11 during this time, when I wasn’t yet feeling too bad. Just didn’t have the energy to write anything about the solutions here. Also managed to solve part 1 of day 12. Since then, nothing.

I still have cough, sneezing every now and then, a feeling of having a flu and am also very tired. The whole Xmas break, as well as new year, and all the days in-between and after was spent just being sick. Didn’t see any family or the kids and their families during the break, to avoid infecting them.

We had great plans to rest and recuperate after the busy work period from September to December; just rest mentally, go skiing and running, eat good food, go sauna, meet the family, etc. So that we’d be well rested when teaching begins next week.

Instead, we are just tired, consumed by this fucking virus. Hopefully no long covid comes out of this, that’d be too much.

Let’s see if I bother to write about the solved AoC puzzles and/or continue to solve the remaining puzzles later on. Because it seems that I have more student projects to supervise than last year, with larger groups. That seems to be a trend nowadays, each year more students, less resources (teachers and hours) to do the work, actually teaching students stuff. This is so bullshit but hey, what can you do.

The student projects start next week, when I am supposed to contact them all and kick them off. Also, next week I need to start grading the Data structures and algorithms course projects, as well as prepare the final exam for that course. That’s already a lot of work to do, in this shape I currently am. Going to take it easy…

Advent of Code quick update

Solved Day 9 part 1 yesterday, but since then I’ve been caught with much work and haven’t had the opportunity to continue in spare/free time. So, I haven’t had the time nor energy to finish part 2.

This is the final teaching week in two programming courses, when students are getting quite active in making sure their work is either ready and acceptable, or they are too late in the schedule and are desperately asking for help to be able to proceed to meet the deadlines. It’s been quite busy, in Zoom breakout rooms, email and our internal support Q&A system…

So, this is probably going to be the AoC week when I’m going to fall behind. I’ll post updates when something gets done, but otherwise it’s radio silence until there is time for this “project”.

Advent of Code Day 8 – Resonant Collinearity

This was surprisingly easy! The part 1 of today’s task was correct on the first run, which is the first time this has happened to me! Usually I make stupid mistakes, not reading the puzzle carefully enough, and rushing to coding with too little planning ahead.

The puzzle part 1 was to find out positions on a map to place antinodes to prevent antennas to emit a signal that makes people 0.1% more likely to buy Easter Bunny brand Imitation Mediocre Chocolate as a Christmas gift.

Calculate the impact of the signal. How many unique locations within the bounds of the map contain an antinode?

A map looks like this:

............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............

Where 0 and A are antennas that are sending on a same frequency, A antennas on their own and 0 antennas theirs, respectively. To prevent their signals, the antinodes ( #)must be placed in line of the antennas, after the first and last pair of antennas, like this:

......#....#
...#....0...
....#0....#.
..#....0....
....0....#..
.#....A.....
...#........
#......#....
........A...
.........A..
..........#.
..........#.

To solve the problem, I implented a Dictionary with key-value -pairs (the [Character: [Point]] below), where the key is an antenna with a specific frequency and value is an array of (x, y) positions on the map where that kind of antennas are placed.

var antinodes: Set<Point> = []
let matrix: Matrix<Character> = parse(data: data)
let antennaMap: [Character: [Point]] = parseAntennaMap(matrix)

After parsing the antennas and their positions into a Matrix, and extracting the antennas and their positions into the dictionary, I could print out the dictionary keys (antennas) with the values (a list of positions for the antennas with the same frequency ):

Antenna A
5:6
8:8
9:9
Antenna 0
1:8
2:5
3:7
4:4

So I could verify that everything is in order for the next step of the puzzle.

I already had a Point structure, having the x and y coordinates, as well as operations to calculate the difference of two points in the coordinate (the distance between them) as well as add together two coordinate points to extrapolate the line between two antennas to position the antinodes inline with the antennas.

So what to do with these? First I needed to handle all the antennas with the same frequency in pairs, to extrapolate the line between them to both directions to add the antinodes. The permutations(ofCount:) algorithm did that. For each pair of antennas, I determined which comes first (point is upper on the map compared to the other) and vice versa.

After that I counted the difference between the first and last point, because the antinode must be place an equal distance away from the antennas. Then I added a point above the first point (first - difference) and below the last point (last + difference).

Then I just added these two antinodes into a Set to get a number of antinodes placed on the map:

for (_, points) in antennaMap {
  let permutations = points.permutations(ofCount: 2)
  for permutation in permutations {
     let first =
     permutation.first! < permutation.last! ? permutation.first! : permutation.last!
    let last = first == permutation.first! ? permutation.last! : permutation.first!
    let difference = last - first
    antinodes.insert(first - difference)
    antinodes.insert(last + difference)
  }
}
antinodes = antinodes.filter({ matrix.contains(y: $0.y, x: $0.x) })
return antinodes.count

Finally I made sure no antinodes were placed outside the map coordinates (using filter to include only those points in the area of the matrix coordinates) and finally returning the count of antinodes placed (the puzzle answer).

Part 2 was just an addition to part 1. Instead of placing one antinode before and after the line of two antennas, place them so that the antinodes are repeated with equal spacing along the line formed by a pair of antennas, until to the edge of the map.

What I first missed in the puzzle instructions was that the positions of the antennas also count in the final answer, not just the antinodes. I did get that the unique positions only are counted (that is why the Set<Point> is used for antinode positions). As I realized this, I just formed a union of antinode and antenna positions, and count of those was the correct answer!

In total, this didn’t take long to solve, thanks to the existing data structures and algorithms, both from Swift, Swift libraries (Swift Algorithms) and my own utilities from previous years. Performance is also OK:

$ swift run -c release AdventOfCode 8 --benchmark
Building for production...
Build of product 'AdventOfCode' complete! (0.28s)
Executing Advent of Code challenge 8...
Part 1: 259
Part 2: 927
Part 1 took 0.000727958 seconds, part 2 took 0.000798333 seconds.

Advent of Code Day 7 – Bridge Repair

The goal for the day was to find out if summing or multiplication would produce the result of an equation, evaluating from left to right (not as usual in maths, evaluating multiplication first). For example:

190: 10 19

Here the result is 190, so it is obvious that multiplication is needed, 10 x 19 is 190. Things get a bit more complicated when there are more numbers:

3267: 81 40 27

Here it is possible to solve the correct result by 81 + 40 * 27 and 81 * 40 + 27 since they both equal 3267. Basically this is a problem where you want to try out different combinations, also called as permutations, of how to use summing and multiplication to produce the correct end result. The input contains some formulas that do not produce the correct result with any permutation, so those are ignored when producing the end sum of the game.

One way to solve this is using recursion – trying different combinations based on the previous evaluation step result with the current number and looking if, after evaluating all the numbers, this produces the correct result.

Simplified, the recursive part looks like the code below. Parameters are:

  • result: The expected end result of the evaluation.
  • intermediate: The intermediate result of the previous + and * operations on the previous numbers, and
  • numbers: the numbers not yet evaluated.

The return value is the result of the evaluation, either the correct result or zero if evaluation didn’t result in the correct result.

func evaluate(_ result: Int, _ intermediate: Int, _ numbers: [Int]) -> Int {
  if numbers.count == 1 {
   if intermediate + numbers.first! == result {
    return result
   } else if intermediate * numbers.first! == result {
    return result
   }
   return 0
  }
  
  let nextNumber = numbers.first!
  
  let summed = intermediate + nextNumber
  let multiplied = intermediate * nextNumber
  
  let evaluateSummed = evaluate(result, summed, Array(numbers.suffix(numbers.count - 1)), useConcatOperator: useConcatOperator)
  if evaluateSummed == result {
   return result
  }
  let evaluateMultiplied = evaluate(result, multiplied, Array(numbers.suffix(numbers.count - 1)), useConcatOperator: useConcatOperator)
  if evaluateMultiplied == result {
   return result
  }
  return 0
}

Recursion must always stop, otherwise it would continue forever – or actually, until stack memory available to the program runs out, producing a stack overflow.

Ending the recursion is handled first; if there is only one number left (the rightmost) we check if the intermediate result of the previous calculations together with the last number produce the expected result either by summing or multiplying. If not, return zero to indicate that this permutation of operations does not work correctly .

If there was more than one number left, both the sum and multiplication is calculated from the previous intermediate result, together with the current number. Then both intermediate values are used — recursively calling evaluate twice with sum and multiplied intermediate — with the rest of the numbers, checking if this produced the result we want. If not, code continues to the end of the function and returns zero — cannot do the thing with this permutation of operations.

I could have optimized the result, finishing the recursion earlier if the produced intermediary sum is greater than the end result. In that case, it is not useful to continue handing the numbers that are still left. Maybe I’ll add that later.

Second part of the puzzle added a third operator, concatenation. In addition to summing or multiplying, two numbers could be also concatenated, e.g. numbers 12 and 34 would result in number 1234.

This was easily added to the code above, just add another else if branch in the beginning and in the end part of the algorithm above, and execute those conditionally (if the function was called from part 2 solution).

Since the non-optimized version was already fast enough, haven’t done any optimization:

$ swift run -c release AdventOfCode 7 --benchmark
Building for production...
Build of product 'AdventOfCode' complete! (0.17s)
Executing Advent of Code challenge 7...
Part 1: 12940396350192
Part 2: 106016735664498
Part 1 took 0.023434208 seconds, part 2 took 0.590302166 seconds.

Now lunch, then some socializing, maybe continuing to Day 8 puzzles today or tomorrow, let’s see…

Advent of Code Day 6 – Guard Gallivant

First part of today’s puzzle was to find out the path of the guard on a map shown below, and count how many distinct positions will the guard visit before leaving the mapped area.

....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...

Guard starts walking upwards from the ^ and the # ‘s are blocks on the path, and when the guard meets one, she always turns right in front of the block.

The part 1 was quite straightforward to implement and didn’t take too long.

In part 2 the goal was to find out in how many places a new obstacle (#) can be placed, so that it causes the guard to walk in circles.

I was working on different solutions, considering using a graph again to see if the graph contains a cycle (there are algorithms for that). Since yesterday was quite a busy day otherwise, I couldn’t focus on solving the puzzle, hence I am one day behind.

Today morning I continued with the part 2 with the ideas I had been thinking about, not needing a Graph. One can detect a cycle when the same turning point is met the second time. So I use a Set<Turn> structure, containing the point and the direction where the turn was taken. Set doesn’t allow you to add a new item that is already in the set, and if this happens, it indicates a cycle in the path.

So I just started to add new obstacles in the Matrix in every possible point that is not a starting point, not already containing an obstacle, and then see if this leads to a cycle in the path, counting those places.

The solution worked with the tests, but when executed with my input data, it always produced too small a number. And I couldn’t find out why! Finally, I searched the internetz and found a comment in Reddit, someone having the same issue and explaining the reason to it:

I was changing direction and going to the next position if there was a blockade in the same iteration, instead of changing direction or moving to the next position. This worked for part 1, but for part 2, this gave me the correct answer for sample input, but for the actual input, the answer was too low, and it took me 4 hours to figure out the issue.

— ajzone007

I was having exactly the same issue! This was a one line fix, not advancing to the next position when direction changes, until the next roll of the loop! This is relatively fast:

$ swift run -c release AdventOfCode 6 --benchmark
Building for production...
Build of product 'AdventOfCode' complete! (0.16s)
Executing Advent of Code challenge 6...
Part 1: 4819
Part 2: 1796
Part 1 took 0.001846167 seconds, part 2 took 0.56900375 seconds.

Here’s the path finding algorithm, used both in part 1 and part 2, throwing an exception when a cycle is met, used in part 2 solution:

private func findPath(
    from start: Point,
    in matrix: Matrix<Character>
) throws -> (path: [Point], turns: Set<Turn>) {
    var direction = Direction.from(matrix[start.y, start.x]!)!
    var currentPoint = start
    var path: [Point] = []
    var turns: Set<Turn> = []
    path.append(start)
    repeat {
      let nextPoint = currentPoint.movedTo(direction)
      if !matrix.contains(y: nextPoint.y, x: nextPoint.x) {
        break
      }
      if matrix[nextPoint.y, nextPoint.x] == "#" {
        direction = direction.turnRight()
        let (inserted, _) = turns.insert(Turn(point: currentPoint, direction: direction))
        if !inserted {
          throw Day06Error.hasCycles
        }
      }
      else {
        currentPoint = nextPoint
      }
      path.append(currentPoint)
    } while true
    return (path, turns)
}

The Set return value is not actually used in the calling code, so that could be taken out.

Then to the Day 7…

Advent of Code Day 5 – Print Queue

Yesterday was a busy day at work and also after, shopping for today: the Independence Day of Finland. Soon I’ll go to see the cannons fire at Linnansaari (Castle Island) with the kids’ families so just a short update on yesterday’s puzzle.

Part 1 was easy and fast to implement. I used last year’s Graph<T> algorithms I implemented to find out which book pages should precede other pages in a list of page numbers. Graph contains the list of pages (vertices) and for each page, those pages that should follow that page (edges).

Second puzzle then… I started with it way too complicated. When failed, I decided to go brute force. The goal was to fix broken page orders, so I thought that I’d just create all the permutations of page orders for a certain print job and try which of those would be correct.

Well, when the input data has page sequences like…:

74,56,86,81,84,44,53,92,12,36,15,66,95,26,71

…and you realize that this sequence of 15 elements alone has 15! permutations, that is 1 307 674 368 000, and the data set has 200+ such sequences, you’ll know that this will take way too much time.

So I left this puzzle alone yesterday and continued this morning, when I woke up after 5 am and couldn’t get any sleep, waiting for the trip to see the cannons fire.

Instead of permutations, the working (and fast) algorithm just goes through the sequence and checks if a page number following the previous is in the edge list of the previous page. If not, the pages are swapped. This is done in a loop that checks if the sequence is still not correct, the checking and swapping continues until the morale improves:

private func fix(_ pages: [Int], using graph: Graph<Int>) -> [Int] {
    var modPages = pages
    repeat {
      for index in 0..<modPages.count - 1 {
        let preceding = modPages[index]
        let following = modPages[index + 1]
        let edges = graph.edges(for: graph.vertex(for: preceding)!)?.map ( {$0.destination.item } )
        if edges != nil && !edges!.isEmpty {
          if !edges!.contains(following) {
            modPages.swapAt(index, index + 1)
          }
        } else {
          modPages.swapAt(index, index + 1)
        }
      }
    } while !isUpdateValid(modPages, graph)
    return modPages
}

This did the trick, and is fast, and the puzzle is solved. Let’s see how today’s puzzle is when I return from the morning trip.

$ swift run -c release AdventOfCode 5 --benchmark
Building for production...
[5/5] Linking AdventOfCode
Build of product 'AdventOfCode' complete! (2.73s)
Executing Advent of Code challenge 5...
Part 1: 4689
Part 2: 6336
Part 1 took 0.006513333 seconds, part 2 took 0.020255375 seconds.

Advent of Code Day 4 – Ceres Search

Today I got to reuse the Matrix<T> generic data structure I implemented last year. Input being a grid of characters like:

MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX

The goal in part 1 of the puzzle was to find out how many times the word “XMAS” appears in the grid, whether it is written left-to-right, right-to-left or downwards or upwards. And diagonally, from left to right and down, or from right to left and up, as well as from left to right and up, or from right to left and down.

The Matrix implementation has an algorithm to rotate the matrix in steps of 90 degrees. So instead of traversing the grid in all those directions, I rotated it 90 degrees to find out the left-right-up-down and back occurrences of XMAS. After each rotation, I just searched let to right, covering the other directions using rotation

The diagonals are handled in the same way, but in each rotation, I only handled the upper right half of the grid, diagonally where the lines were longer than three characters. I first made the mistake of handling the corner-to-corner line in all rotated diagonals, having too large count of occurrences. When I got to see this mistake, I handled that corner-to-corner diagonal only in one of the rotations.

In all of those cases I ended up with a line, string of characters. What I needed to do then was to find out how many times the line (and the line in reversed order) contains the XMAS.

Part 2 was much more simpler than part 1, so that was quickly solved. The goal was to count how many crosses like this…:

M.S
.A.
M.S

…the matrix contains.

This required just two loops within, starting from the first line and column from the edge, finishing on second-to-last row and column, check if the cell contains an A and then check if the opposing corners are different and contain M and S.

This was quick on my work laptop (MacBook Pro M2, 2022):

$ swift run -c release AdventOfCode 4 --benchmark
Building for production...
[1/1] Write swift-version--1AB21518FC5DEDBE.txt
Build of product 'AdventOfCode' complete! (0.16s)
Executing Advent of Code challenge 4...
Part 1: 2549
Part 2: 2003
Part 1 took 0.014584666 seconds, part 2 took 0.002027958 seconds.

The matrix rotations take time, obviously, so part 1 was slower. I considered counting the results of rotated matrices in parallel, but decided not to. That may or may not produce any speed advantage, don’t know… Also could’ve tried to do not rotating in part 1, and just go all in to the matrix from all directions and back to find the words. Well’ it works so I’ll let it be.

Advent of Code Day 3 revisited

So, originally I did this the old school style, parsing the input string character by character, managing the state of processing while discovering the elements from the input string.

Tonight I reimplemented part 1 using #Swift RegexBuilder. I’ve never been comfortable in using regular expressions (regex) but wanted to give a try.

Swift RegexBuilder code snippet showing how to pick up the mul instructions with parameters from the day's puzzle input.
Snippet of RegexBuilder code showing how to specify what elements to pick from the input string.

Got it working nicely after some hiccups, as usual – wondered why the sum of mul operations was smaller than expected, and it was because I had Repeat(1...2) instead of Repeat(1...3) in the code. Didn’t notice that some mul parameters were three numbers, since in puzzle examples there were only two numbers. So the regex was only picking first two numbers at most from the input :facepalm:

Learned something new. Maybe regexes are not so terrifying things after all…

An illustration depicting a muscular figure reminiscent of a Greek warrior, labeled "PROGRAMMERS," with an arrow lodged in its leg labeled "REGEX." The style is simplistic and humorous, emphasizing the struggles programmers face with regular expressions.

Advent of Code Day 3 – Mull It Over

Today’s puzzle was about parsing. Quite straightforward to implement, having done parsing earlier for different types of structured strings, like tsv, csv, json, xml, etc.

In part 1, I anticipated that part 2 would add parsing some other instructions than mul so I did implement it so. There is an Instruction structure that specifies:

  • the instruction name
  • expected number of parameters and
  • the list of parameter values

Then the instruction is able to evaluate the result. Parsing returns an array of Instructions to execute, and then they are executed, accumulating the result of each mul instruction evaluated:

private struct Instruction {
  let instruction: String
  let params: Int
  var values: [Int] = []
    
  var result: Int {
    if instruction == "mul" {
      return values.reduce(1, *)
    }
    return 0
  }
}

In part 2, the execution just changes the state; after do instruction, following instructions are evaluated, and after don't, the instructions are ignored, until next do instruction:

func part1() -> Any {
    let instructions = parse(data, 
                     for: [
                        Instruction(instruction: "mul", params: 2)
                     ])
    return execute(instructions)
}

func part2() -> Any {
    let instructions = parse(data,
                     for: [
                      Instruction(instruction: "mul", params: 2),
                      Instruction(instruction: "do", params: 0),
                      Instruction(instruction: "don't", params: 0)
                     ])
    return execute(instructions)
}

Benchmarked:

$ swift run -c release AdventOfCode 3 --benchmark
Building for production...
[6/6] Linking AdventOfCode
Build of product 'AdventOfCode' complete! (2.55s)
Executing Advent of Code challenge 3...
Part 1: 188116424
Part 2: 104245808
Part 1 took 0.00304425 seconds, part 2 took 0.004164459 seconds.