Skip to content

Commit

Permalink
Day06
Browse files Browse the repository at this point in the history
  • Loading branch information
jakcharvat committed Dec 6, 2024
1 parent 97ff614 commit b1dc904
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 0 deletions.
17 changes: 17 additions & 0 deletions day06/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Day 06

First part was ok. Simulate the walk of the guard and count the number of squares they visit.
No fancy tricks, just the input parsing was painful as usual.

The second part could definitely be implemented better. Currently, I find the guard's path from the
first part, and attempt to place a block at every point of the path. For every such block I then
simulate the guard's path again to see if a cycle forms.

This could perhaps be optimised by finding all paths that lead to starting position, then
simulating a single walk of the guard and counting places where we overlap the already walked path
or the tree that leads to the starting position.

If n is the side length of the map, 130 for my input, my current implementation runs in
(walk_length * n^2) time, which in the worst case is n^4. It takes around 5s on my machine. The
solution with first finding the states which lead to the initial position should be a single maze
search and a single simulation, so should run in n^2 time.
20 changes: 20 additions & 0 deletions day06/gleam.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name = "day06"
version = "1.0.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
#
# description = ""
# licences = ["Apache-2.0"]
# repository = { type = "github", user = "", repo = "" }
# links = [{ title = "Website", href = "" }]
#
# For a full reference of all the available options, you can have a look at
# https://gleam.run/writing-gleam/gleam-toml/.

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
simplifile = ">= 2.2.0 and < 3.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
130 changes: 130 additions & 0 deletions day06/input.txt

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions day06/manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This file was generated by Gleam
# You typically do not need to edit this file

packages = [
{ name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" },
{ name = "gleam_stdlib", version = "0.45.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "206FCE1A76974AECFC55AEBCD0217D59EDE4E408C016E2CFCCC8FF51278F186E" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
{ name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" },
]

[requirements]
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
simplifile = { version = ">= 2.2.0 and < 3.0.0" }
10 changes: 10 additions & 0 deletions day06/small-in.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...
162 changes: 162 additions & 0 deletions day06/src/day06.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import gleam/bool
import gleam/io
import gleam/list
import gleam/result
import gleam/set
import gleam/string
import simplifile

fn force_unwrap(r: Result(a, e)) -> a {
result.lazy_unwrap(r, fn() { panic })
}

type Coord {
Coord(row: Int, col: Int)
}

type Map {
Map(rows: Int, cols: Int, map: set.Set(Coord))
}

type Dir {
Up
Right
Down
Left
}

fn dir_from_string(str: String) {
case str {
"^" -> Ok(Up)
">" -> Ok(Right)
"v" -> Ok(Down)
"<" -> Ok(Left)
_ -> Error(Nil)
}
}

fn right(from dir: Dir) -> Dir {
case dir {
Up -> Right
Right -> Down
Down -> Left
Left -> Up
}
}

fn step(dir: Dir) -> Coord {
case dir {
Up -> Coord(-1, 0)
Right -> Coord(0, 1)
Down -> Coord(1, 0)
Left -> Coord(0, -1)
}
}

fn add_coord(a: Coord, b: Coord) -> Coord {
Coord(a.row + b.row, a.col + b.col)
}

type Guard {
Guard(pos: Coord, dir: Dir)
}

fn result_when(condition: Bool, give good_val: a) -> Result(a, Nil) {
case condition {
True -> Ok(good_val)
False -> Error(Nil)
}
}

fn parse_input(input: String) -> #(Map, Guard) {
let lines =
input
|> string.split("\n")

let els =
lines
|> list.index_map(fn(row, y) {
list.index_map(string.split(row, ""), fn(el, x) { #(el, Coord(y, x)) })
})
|> list.flatten

let map =
els
|> list.filter_map(fn(x) { result_when(x.0 == "#", give: x.1) })
|> set.from_list

let height = list.length(lines)
let width = string.length(list.first(lines) |> force_unwrap)
let map = Map(height, width, map)

let assert [guard] =
els
|> list.filter_map(fn(x) {
dir_from_string(x.0) |> result.map(fn(dir) { Guard(x.1, dir) })
})

#(map, guard)
}

fn map_contains(map: Map, coord: Coord) -> Bool {
coord.row >= 0
&& coord.col >= 0
&& coord.row < map.rows
&& coord.col < map.cols
}

fn guard_step(map: Map, guard: Guard) -> Guard {
let step = add_coord(guard.pos, step(guard.dir))
use <- bool.guard(
when: !set.contains(map.map, step),
return: Guard(step, guard.dir),
)

guard_step(map, Guard(guard.pos, right(guard.dir)))
}

fn get_visited(
map: Map,
guard: Guard,
visited: set.Set(Coord),
) -> set.Set(Coord) {
use <- bool.guard(when: !map_contains(map, guard.pos), return: visited)
let visited = set.insert(visited, guard.pos)

get_visited(map, guard_step(map, guard), visited)
}

fn check_loop(map: Map, guard: Guard, visited: set.Set(Guard)) -> Bool {
use <- bool.guard(when: !map_contains(map, guard.pos), return: False)
use <- bool.guard(when: set.contains(visited, guard), return: True)
let visited = set.insert(visited, guard)

check_loop(map, guard_step(map, guard), visited)
}

pub fn part1(input: String) -> Int {
let #(map, guard) = parse_input(input)
get_visited(map, guard, set.new()) |> set.size
}

pub fn part2(input: String) -> Int {
let #(map, guard) = parse_input(input)
let walk = get_visited(map, guard, set.new())

walk
|> set.filter(fn(coord) { coord != guard.pos })
|> set.filter(fn(coord) {
check_loop(Map(..map, map: set.insert(map.map, coord)), guard, set.new())
})
|> set.size
}

pub fn main() {
let input =
simplifile.read("input.txt")
|> result.lazy_unwrap(fn() { panic })
|> string.trim

io.println("Part 1: " <> string.inspect(part1(input)))
io.println("Part 2: " <> string.inspect(part2(input)))
}
27 changes: 27 additions & 0 deletions day06/test/day06_test.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import gleam/result
import gleam/string
import gleeunit
import gleeunit/should
import simplifile

import day06

fn input() -> String {
simplifile.read("small-in.txt")
|> result.lazy_unwrap(fn() { panic })
|> string.trim
}

pub fn main() {
gleeunit.main()
}

pub fn part1_test() {
day06.part1(input())
|> should.equal(41)
}

pub fn part2_test() {
day06.part2(input())
|> should.equal(6)
}

0 comments on commit b1dc904

Please sign in to comment.