DEV Community

Caleb Weeks
Caleb Weeks

Posted on

Advent of Code #6 (in Gleam)

Today was the first day I decided not to stay up to solve the puzzles right as they come out. Where I live, the puzzles come out at midnight, and for the first few days, I'm usually pretty confident that I can knock out a solution in about half an hour to an hour.

This puzzle was definitely a step up, particularly for part 2. It was the first time this year where I submitted a wrong answer and the first time I had to wait more than a second for the code to run. But I haven't had to reach for any advanced techniques yet. My solution is still pretty much the most straightforward approach.

Typically, these sorts of puzzles where you have to check a lot of things will start to benefit from divide and conquer techniques, caching strategies, or analytical approaches. The one 'optimization' I applied is I only tried placing new barriers along the standard route, but this is only a reduction of about 75% of the search space, which isn't really the defining dimension of the problem.

The other thing I did differently in this solution compared to previous solutions is using types and records. This helped me keep track in my head of the different pieces of data, and provides a nicer interface for pattern matching and accessing record fields. Here's my solution:

import gleam/int import gleam/io import gleam/list import gleam/string import gleam/result import gleam/option import gleam/set.{type Set} import gleam/dict.{type Dict, insert} import simplifile as file const example = " ....#..... .........# .......... ..#....... .......#.. .......... .#..^..... ........#. #......... ......#... " pub fn main() { let assert Ok(input) = file.read("input") let assert 41 = part1(example) let assert 6 = part2(example) part1(input) |> int.to_string |> io.println part2(input) |> int.to_string |> io.println } type Coord = #(Int, Int) type Direction { Up Down Left Right } type Guard { Guard(position: Coord, direction: Direction) } type Space { Blank Barrier } type Map = Dict(Coord, Space) fn parse_input(input: String) -> #(Map, Guard) { input |> string.trim |> string.split("\n") |> list.index_fold(#(dict.new(), Guard(#(0, 0), Up)), fn(init, line, x) { line |> string.to_graphemes |> list.index_fold(init, fn(init, char, y) { let #(map, start) = init case char { "." -> #(insert(map, #(x, y), Blank), start) "#" -> #(insert(map, #(x, y), Barrier), start) "^" -> #(insert(map, #(x, y), Blank), Guard(#(x, y), Up)) "v" -> #(insert(map, #(x, y), Blank), Guard(#(x, y), Down)) ">" -> #(insert(map, #(x, y), Blank), Guard(#(x, y), Right)) "<" -> #(insert(map, #(x, y), Blank), Guard(#(x, y), Left)) _ -> init } }) }) } fn rotate(guard: Guard) -> Guard { case guard { Guard(position, Up) -> Guard(position, Right) Guard(position, Right) -> Guard(position, Down) Guard(position, Down) -> Guard(position, Left) Guard(position, Left) -> Guard(position, Up) } } fn move(map: Map, guard: Guard) -> Result(Guard, Nil) { let Guard(#(x, y), direction) = guard let next_position = case direction { Up -> #(x - 1, y) Down -> #(x + 1, y) Left -> #(x, y - 1) Right -> #(x, y + 1) } map |> dict.get(next_position) |> result.map(fn(space) { case space { Blank -> Guard(next_position, direction) Barrier -> rotate(guard) } }) } fn standard_route(visited: Set(Coord), map: Map, guard: Guard) -> Set(Coord) { case move(map, guard) { Ok(guard) -> standard_route(set.insert(visited, guard.position), map, guard) _ -> visited } } fn part1(input: String) -> Int { let #(map, start) = parse_input(input) set.new() |> set.insert(start.position) |> standard_route(map, start) |> set.size } type Route { Exit Loop } fn route(visited: Set(Guard), map: Map, guard: Guard) -> Route { case move(map, guard) { Ok(guard) -> case set.contains(visited, guard) { True -> Loop False -> route(set.insert(visited, guard), map, guard) } _ -> Exit } } fn part2(input: String) -> Int { let #(map, start) = parse_input(input) set.new() |> set.delete(start.position) |> standard_route(map, start) |> set.fold(0, fn(count, coord) { let new_map = map |> dict.upsert(coord, fn(space) { space |> option.map(fn(_) { Barrier }) |> option.unwrap(Blank) }) case route(set.new(), new_map, start) { Loop -> count + 1 Exit -> count } }) } 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)