Skip to content

Add 2023 day 23 solution and input #545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions 2023/input/day23
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#.#####################
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently these inputs shouldn't be shared (look it up), so now we will no longer accept them :)

Copy link
Contributor Author

@stewSquared stewSquared Jan 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know! These aren't my inputs. They're the example :)

I did the same thing for Day 17

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well I think even the sample input is still protected

#.......#########...###
#######.#########.#.###
###.....#.>.>.###.#.###
###v#####.#v#.###.#.###
###.>...#.#.#.....#...#
###v###.#.#.#########.#
###...#.#.#.......#...#
#####.#.#.#######.#.###
#.....#.#.#.......#...#
#.#####.#.#.#########v#
#.#...#...#...###...>.#
#.#.#v#######v###.###v#
#...#.>.#...>.>.#.###.#
#####v#.#.###v#.#.###.#
#.....#...#...#.#.#...#
#.#########.###.#.#.###
#...###...#...#...#.###
###.###.#.###v#####v###
#...#...#.#.>.>.#.>.###
#.###.###.#.###.#.#v###
#.....###...###...#...#
#####################.#
123 changes: 123 additions & 0 deletions 2023/src/day23.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package day23
// based on solution from https://github.com/stewSquared/adventofcode/blob/src/main/scala/2023/Day23.worksheet.sc

import collection.immutable.BitSet

import locations.Directory.currentDir
import inputs.Input.loadFileSync

@main def part1: Unit =
given maze: Maze = Maze(loadInput())
println(s"The solution is $longestDownhillHike")

@main def part2: Unit =
given maze: Maze = Maze(loadInput())
println(s"The solution is $longestHike")

def loadInput(): Vector[Vector[Char]] = Vector.from:
val file = loadFileSync(s"$currentDir/../input/day23")
for line <- file.split("\n")
yield line.toVector

enum Dir:
case N, S, E, W

def turnRight = this match
case Dir.N => E
case Dir.E => S
case Dir.S => W
case Dir.W => N

def turnLeft = this match
case Dir.N => W
case Dir.W => S
case Dir.S => E
case Dir.E => N

case class Point(x: Int, y: Int):
def dist(p: Point) = math.abs(x - p.x) + math.abs(y - p.y)
def adjacent = List(copy(x = x + 1), copy(x = x - 1), copy(y = y + 1), copy(y = y - 1))

def move(dir: Dir) = dir match
case Dir.N => copy(y = y - 1)
case Dir.S => copy(y = y + 1)
case Dir.E => copy(x = x + 1)
case Dir.W => copy(x = x - 1)

case class Maze(grid: Vector[Vector[Char]]):

def apply(p: Point): Char = grid(p.y)(p.x)

val xRange: Range = grid.head.indices
val yRange: Range = grid.indices

def points: Iterator[Point] = for
y <- yRange.iterator
x <- xRange.iterator
yield Point(x, y)

val walkable: Set[Point] = points.filter(p => grid(p.y)(p.x) != '#').toSet
val start: Point = walkable.minBy(_.y)
val end: Point = walkable.maxBy(_.y)

val junctions: Set[Point] = walkable.filter: p =>
Dir.values.map(p.move).count(walkable) > 2
.toSet + start + end

val slopes = Map.from[Point, Dir]:
points.collect:
case p if apply(p) == '^' => p -> Dir.N
case p if apply(p) == 'v' => p -> Dir.S
case p if apply(p) == '>' => p -> Dir.E
case p if apply(p) == '<' => p -> Dir.W

def connectedJunctions(pos: Point)(using maze: Maze) = List.from[(Point, Int)]:
def walk(pos: Point, dir: Dir): Option[Point] =
val p = pos.move(dir)
Option.when(maze.walkable(p) && maze.slopes.get(p).forall(_ == dir))(p)

def search(pos: Point, facing: Dir, dist: Int): Option[(Point, Int)] =
if maze.junctions.contains(pos) then Some(pos, dist) else
val adjacentSearch = for
nextFacing <- LazyList(facing, facing.turnRight, facing.turnLeft)
nextPos <- walk(pos, nextFacing)
yield search(nextPos, nextFacing, dist + 1)

if adjacentSearch.size == 1 then adjacentSearch.head else None

for
d <- Dir.values
p <- walk(pos, d)
junction <- search(p, d, 1)
yield junction

def longestDownhillHike(using maze: Maze): Int =
def search(pos: Point, dist: Int)(using maze: Maze): Int =
if pos == maze.end then dist else
connectedJunctions(pos).foldLeft(0):
case (max, (n, d)) => max.max(search(n, dist + d))

search(maze.start, 0)

def longestHike(using maze: Maze): Int =
type Index = Int

val indexOf: Map[Point, Index] =
maze.junctions.toList.sortBy(_.dist(maze.start)).zipWithIndex.toMap

val adjacent: Map[Index, List[(Index, Int)]] =
maze.junctions.toList.flatMap: p1 =>
connectedJunctions(p1).flatMap: (p2, d) =>
val forward = indexOf(p1) -> (indexOf(p2), d)
val reverse = indexOf(p2) -> (indexOf(p1), d)
List(forward, reverse)
.groupMap(_._1)(_._2)

def search(junction: Index, visited: BitSet, totalDist: Int): Int =
if junction == indexOf(maze.end) then totalDist else
adjacent(junction).foldLeft(0):
case (longest, (nextJunct, dist)) =>
if visited(nextJunct) then longest else
longest.max(search(nextJunct, visited + nextJunct, totalDist + dist))

search(indexOf(maze.start), BitSet.empty, 0)