diff --git a/gleam.toml b/gleam.toml index 60645d4..6b0bf25 100644 --- a/gleam.toml +++ b/gleam.toml @@ -5,6 +5,7 @@ version = "1.0.0" gleam_stdlib = ">= 0.34.0 and < 2.0.0" stdin = ">= 1.1.4 and < 2.0.0" gleam_yielder = ">= 1.1.0 and < 2.0.0" +parallel_map = ">= 2.1.0 and < 3.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index edbf33a..a4bf2c2 100644 --- a/manifest.toml +++ b/manifest.toml @@ -2,9 +2,12 @@ # You typically do not need to edit this file packages = [ + { name = "gleam_erlang", version = "0.33.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "A1D26B80F01901B59AABEE3475DD4C18D27D58FA5C897D922FCB9B099749C064" }, + { name = "gleam_otp", version = "0.14.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "5A8CE8DBD01C29403390A7BD5C0A63D26F865C83173CF9708E6E827E53159C65" }, { name = "gleam_stdlib", version = "0.45.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "206FCE1A76974AECFC55AEBCD0217D59EDE4E408C016E2CFCCC8FF51278F186E" }, { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, + { name = "parallel_map", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "parallel_map", source = "hex", outer_checksum = "DE2BA9878728EF9EE34BE83FEDC7A18A1ABE4B2AC1E79C710E3E5D95F5E73404" }, { name = "stdin", version = "1.1.4", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "stdin", source = "hex", outer_checksum = "04C04035F2A4CEEFB023837249649CD25F9A9CF5A45F9947C4D0462428A4677D" }, ] @@ -12,4 +15,5 @@ packages = [ gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +parallel_map = { version = ">= 2.1.0 and < 3.0.0" } stdin = { version = ">= 1.1.4 and < 2.0.0" } diff --git a/sample/day6-sample.txt b/sample/day6-sample.txt new file mode 100644 index 0000000..a4eb402 --- /dev/null +++ b/sample/day6-sample.txt @@ -0,0 +1,10 @@ +....#..... +.........# +.......... +..#....... +.......#.. +.......... +.#..^..... +........#. +#......... +......#... diff --git a/src/day6_p1.gleam b/src/day6_p1.gleam new file mode 100644 index 0000000..a6ddf88 --- /dev/null +++ b/src/day6_p1.gleam @@ -0,0 +1,137 @@ +import gleam/io +import gleam/iterator +import gleam/list.{filter, map} +import gleam/pair +import gleam/result +import gleam/string +import stdin.{stdin} + +pub fn main() { + stdin() + |> read_input + |> traverse + |> io.debug +} + +fn traverse(params: #(Map, #(Int, Int))) -> Int { + let #(map, guard) = params + + traverse_loop([], map, guard, Up) + |> list.unique + |> list.length +} + +fn traverse_loop( + visited: List(#(Int, Int)), + map: Map, + position: #(Int, Int), + direction: Direction, +) -> List(#(Int, Int)) { + case position { + #(x, _) if x < 0 -> visited + #(x, _) if x >= map.width -> visited + #(_, y) if y < 0 -> visited + #(_, y) if y >= map.height -> visited + _ -> { + let next = walk(position, direction) + case + map.obstacles + |> list.find(fn(x) { x == next }) + { + Ok(_) -> traverse_loop(visited, map, position, turn(direction)) + _ -> traverse_loop([position, ..visited], map, next, direction) + } + } + } +} + +type Direction { + Up + Left + Right + Down +} + +fn walk(pos: #(Int, Int), dir: Direction) -> #(Int, Int) { + let #(x, y) = pos + case dir { + Up -> #(x, y - 1) + Down -> #(x, y + 1) + Left -> #(x - 1, y) + Right -> #(x + 1, y) + } +} + +fn turn(dir: Direction) -> Direction { + case dir { + Up -> Right + Right -> Down + Down -> Left + Left -> Up + } +} + +type Map { + Map(width: Int, height: Int, obstacles: List(#(Int, Int))) +} + +fn read_input(it: iterator.Iterator(String)) -> #(Map, #(Int, Int)) { + let maptxt = + it + |> iterator.to_list + |> map(string.trim) + |> filter(not(string.is_empty)) + + let assert Ok(width) = list.first(maptxt) |> result.map(string.length) + let height = list.length(maptxt) + let obstacles = maptxt |> list.index_fold([], find_obstacles) + + let assert Ok(guard) = + maptxt + |> find_index(fn(line, y) { + use x <- result.try( + line + |> string.to_graphemes + |> find_index(fn(chr, x) { + case chr { + "^" -> Ok(x) + _ -> Error(Nil) + } + }), + ) + Ok(#(x, y)) + }) + + #(Map(width, height, obstacles), guard) +} + +fn find_obstacles( + acc: List(#(Int, Int)), + line: String, + y: Int, +) -> List(#(Int, Int)) { + line + |> string.to_graphemes + |> list.index_fold(acc, fn(cur, chr, x) { + case chr { + "#" -> [#(x, y), ..cur] + _ -> cur + } + }) +} + +fn find_index( + lst: List(a), + apply: fn(a, Int) -> Result(b, Nil), +) -> Result(b, Nil) { + lst + |> list.index_map(pair.new) + |> list.find_map(fn(tup) { + let #(elem, index) = tup + apply(elem, index) + }) +} + +fn not(a: fn(a) -> Bool) { + fn(param: a) -> Bool { !a(param) } +} diff --git a/src/day6_p2.gleam b/src/day6_p2.gleam new file mode 100644 index 0000000..0b9d726 --- /dev/null +++ b/src/day6_p2.gleam @@ -0,0 +1,172 @@ +import gleam/dict.{type Dict} +import gleam/int +import gleam/io +import gleam/iterator +import gleam/list.{filter, map} +import gleam/pair +import gleam/result +import gleam/string +import parallel_map +import stdin.{stdin} + +pub fn main() { + stdin() + |> read_input + |> obstruct + |> io.debug +} + +fn obstruct(params: #(Map, #(Int, Int))) -> Int { + let #(map, guard) = params + + // Skip first step (guard starting spot) + let assert Finite([_, ..steps]) = + traverse_loop_check(dict.new(), map, guard, Up) + + // Beware, this one takes a while... + steps + |> parallel_map.list_pmap( + fn(obstruction) { + let new_map = Map(map.width, map.height, [obstruction, ..map.obstacles]) + traverse_loop_check(dict.new(), new_map, guard, Up) + }, + parallel_map.MatchSchedulersOnline, + 5000, + ) + |> list.count(fn(x) { x == Ok(Loop) }) +} + +type Patrol { + Finite(visited: List(#(Int, Int))) + Loop +} + +fn traverse_loop_check( + visited: Dict(#(#(Int, Int), Direction), Int), + map: Map, + position: #(Int, Int), + direction: Direction, +) -> Patrol { + case dict.has_key(visited, #(position, direction)) { + True -> Loop + False -> + case position { + #(x, _) if x < 0 -> Finite(visited |> to_steps) + #(x, _) if x >= map.width -> Finite(visited |> to_steps) + #(_, y) if y < 0 -> Finite(visited |> to_steps) + #(_, y) if y >= map.height -> Finite(visited |> to_steps) + _ -> { + let next = walk(position, direction) + case + map.obstacles + |> list.find(fn(x) { x == next }) + { + Ok(_) -> + traverse_loop_check(visited, map, position, turn(direction)) + _ -> + traverse_loop_check( + visited |> dict.insert(#(position, direction), 1), + map, + next, + direction, + ) + } + } + } + } +} + +fn to_steps(visited: Dict(#(a, _), _)) -> List(a) { + visited |> dict.keys |> list.map(pair.first) |> list.unique +} + +type Direction { + Up + Left + Right + Down +} + +fn walk(pos: #(Int, Int), dir: Direction) -> #(Int, Int) { + let #(x, y) = pos + case dir { + Up -> #(x, y - 1) + Down -> #(x, y + 1) + Left -> #(x - 1, y) + Right -> #(x + 1, y) + } +} + +fn turn(dir: Direction) -> Direction { + case dir { + Up -> Right + Right -> Down + Down -> Left + Left -> Up + } +} + +type Map { + Map(width: Int, height: Int, obstacles: List(#(Int, Int))) +} + +fn read_input(it: iterator.Iterator(String)) -> #(Map, #(Int, Int)) { + let maptxt = + it + |> iterator.to_list + |> map(string.trim) + |> filter(not(string.is_empty)) + + let assert Ok(width) = list.first(maptxt) |> result.map(string.length) + let height = list.length(maptxt) + let obstacles = maptxt |> list.index_fold([], find_obstacles) + + let assert Ok(guard) = + maptxt + |> find_index(fn(line, y) { + use x <- result.try( + line + |> string.to_graphemes + |> find_index(fn(chr, x) { + case chr { + "^" -> Ok(x) + _ -> Error(Nil) + } + }), + ) + Ok(#(x, y)) + }) + + #(Map(width, height, obstacles), guard) +} + +fn find_obstacles( + acc: List(#(Int, Int)), + line: String, + y: Int, +) -> List(#(Int, Int)) { + line + |> string.to_graphemes + |> list.index_fold(acc, fn(cur, chr, x) { + case chr { + "#" -> [#(x, y), ..cur] + _ -> cur + } + }) +} + +fn find_index( + lst: List(a), + apply: fn(a, Int) -> Result(b, Nil), +) -> Result(b, Nil) { + lst + |> list.index_map(pair.new) + |> list.find_map(fn(tup) { + let #(elem, index) = tup + apply(elem, index) + }) +} + +fn not(a: fn(a) -> Bool) { + fn(param: a) -> Bool { !a(param) } +}