aboutsummaryrefslogtreecommitdiff
path: root/aoc2023-gleam/src/day5/solve.gleam
diff options
context:
space:
mode:
authorJ.J <thechairman@thechairman.info>2024-05-30 21:49:58 -0400
committerJ.J <thechairman@thechairman.info>2024-05-30 21:49:58 -0400
commit231c2b688d1e6cf0846d46e883da30e042a9c6cf (patch)
tree98a6d3a461fe190b38b2cf33a708a1d01703fa70 /aoc2023-gleam/src/day5/solve.gleam
parentfe088aa5778dcdbaab4dd8d4a7395a91c444b45c (diff)
parenta2c2b728ec6051323ed937f54816089cd2ae9d20 (diff)
downloadgleam_aoc-231c2b688d1e6cf0846d46e883da30e042a9c6cf.tar.gz
gleam_aoc-231c2b688d1e6cf0846d46e883da30e042a9c6cf.zip
Merge branch 'main' of https://github.com/hunkyjimpjorps/AdventOfCode
Diffstat (limited to 'aoc2023-gleam/src/day5/solve.gleam')
-rw-r--r--aoc2023-gleam/src/day5/solve.gleam162
1 files changed, 162 insertions, 0 deletions
diff --git a/aoc2023-gleam/src/day5/solve.gleam b/aoc2023-gleam/src/day5/solve.gleam
new file mode 100644
index 0000000..7c05310
--- /dev/null
+++ b/aoc2023-gleam/src/day5/solve.gleam
@@ -0,0 +1,162 @@
+import adglent.{First, Second}
+import gleam/io
+import gleam/string
+import gleam/result
+import gleam/list.{Continue, Stop}
+import gleam/int
+import gleam/function
+
+// Types -------------------------------------------------------------------------------------------
+
+pub type Almanac {
+ Almanac(seeds: List(Int), mappers: List(Mapper))
+}
+
+pub type MappingRange {
+ MRange(start: Int, end: Int, offset: Int)
+}
+
+pub type SeedRange {
+ SRange(start: Int, end: Int)
+}
+
+type Mapper =
+ List(MappingRange)
+
+// Parsing -----------------------------------------------------------------------------------------
+
+fn parse_input(input: String) {
+ let assert ["seeds: " <> raw_seeds, ..raw_mappers] =
+ string.split(input, on: "\n\n")
+
+ let seeds = string_to_int_list(raw_seeds)
+ let mappers =
+ list.map(
+ raw_mappers,
+ function.compose(string.split(_, on: "\n"), parse_mapper),
+ )
+ Almanac(seeds, mappers)
+}
+
+fn string_to_int_list(str: String) {
+ str
+ |> string.split(on: " ")
+ |> list.map(int.parse)
+ |> result.values
+}
+
+fn parse_mapper(strs: List(String)) -> Mapper {
+ let assert [_, ..raw_ranges] = strs
+ list.map(raw_ranges, parse_mrange)
+ |> list.sort(fn(a, b) { int.compare(a.start, b.start) })
+}
+
+fn parse_mrange(str: String) -> MappingRange {
+ let assert [destination, source, range_width] = string_to_int_list(str)
+ MRange(source, source + range_width - 1, destination - source)
+}
+
+// Part 1 ------------------------------------------------------------------------------------------
+
+pub fn part1(input: String) {
+ let Almanac(seeds, mappers) = parse_input(input)
+
+ list.map(seeds, list.fold(over: mappers, from: _, with: correspond))
+ |> list.reduce(int.min)
+ |> result.unwrap(0)
+ |> string.inspect
+}
+
+fn correspond(n: Int, mapper: Mapper) {
+ use acc, mrange <- list.fold_until(over: mapper, from: n)
+ case mrange.start <= acc && acc <= mrange.end {
+ True -> Stop(acc + mrange.offset)
+ False -> Continue(acc)
+ }
+}
+
+// Part 2 ------------------------------------------------------------------------------------------
+
+pub fn part2(input: String) {
+ let Almanac(seeds, mappers) = parse_input(input)
+
+ let assert [SRange(answer, _), ..] =
+ seeds
+ |> list.sized_chunk(into: 2)
+ |> list.map(fn(chunk) {
+ let assert [start, length] = chunk
+ [SRange(start, start + length - 1)]
+ |> remap_all_seed_ranges(mappers)
+ })
+ |> list.flatten()
+ |> list.sort(fn(a, b) { int.compare(a.start, b.start) })
+
+ string.inspect(answer)
+}
+
+fn remap_all_seed_ranges(srs: List(SeedRange), mappers: List(Mapper)) {
+ case mappers {
+ [] -> srs
+ [mapper, ..rest] ->
+ list.flat_map(srs, remap_range(_, mapper))
+ |> remap_all_seed_ranges(rest)
+ }
+}
+
+fn remap_range(r: SeedRange, mapper: Mapper) -> List(SeedRange) {
+ do_remap_range(r, mapper, [])
+}
+
+fn transform_range(r: SeedRange, mapper: MappingRange) -> SeedRange {
+ SRange(r.start + mapper.offset, r.end + mapper.offset)
+}
+
+fn do_remap_range(r: SeedRange, mapper: Mapper, acc: List(SeedRange)) {
+ case mapper {
+ // no more mappings -> no mapping covers this range
+ [] -> [r, ..acc]
+ // range is to the left of current mapping -> no mapping covers this range
+ [m, ..] if r.end < m.start -> [r, ..acc]
+ // range is to the right of current mapping -> move to next mapping
+ [m, ..ms] if r.start > m.end -> do_remap_range(r, ms, acc)
+ // range is fully inside mapping -> range is transformed
+ [m, ..] if r.start >= m.start && r.end <= m.end -> [
+ transform_range(r, m),
+ ..acc
+ ]
+ // range overlaps start but not end -> left side not transformed, right side transformed
+ [m, ..] if r.start < m.start && r.end <= m.end -> [
+ SRange(r.start, m.start - 1),
+ transform_range(SRange(m.start, r.end), m),
+ ..acc
+ ]
+ // range overlaps end but not start -> left side transformed, right side moves to next mapping
+ [m, ..ms] if r.start >= m.start && r.end > m.end ->
+ do_remap_range(SRange(m.end + 1, r.end), ms, [
+ transform_range(SRange(r.start, m.end), m),
+ ..acc
+ ])
+ // mapping is fully inside range -> left not transformed, middle transformed, right to next
+ [m, ..ms] ->
+ do_remap_range(SRange(m.end + 1, r.end), ms, [
+ SRange(r.start, m.start - 1),
+ transform_range(SRange(m.start, m.end), m),
+ ..acc
+ ])
+ }
+}
+
+pub fn main() {
+ let assert Ok(part) = adglent.get_part()
+ let assert Ok(input) = adglent.get_input("5")
+ case part {
+ First ->
+ part1(input)
+ |> adglent.inspect
+ |> io.println
+ Second ->
+ part2(input)
+ |> adglent.inspect
+ |> io.println
+ }
+}