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…