~ihabunek/aoc2022

5287220958764729e401c9549b5fd67dfa2a7b54 — Ivan Habunek 1 year, 8 months ago b84630e
Day 12
M .gitignore => .gitignore +1 -0
@@ 2,3 2,4 @@
*.ez
build
erl_crash.dump
/tmp/
\ No newline at end of file

A input/day12.example.txt => input/day12.example.txt +5 -0
@@ 0,0 1,5 @@
Sabqponm
abcryxxl
accszExk
acctuvwj
abdefghi
\ No newline at end of file

A input/day12.txt => input/day12.txt +41 -0
@@ 0,0 1,41 @@
abccccccaaccaaccccaaaaacccccaaaaccccccccccccccccccccccccccccccccaaaaaaaaaaaaaaaaaaaccccccccccccccccaaaccccccccccccaacccccccccccccccccccccccccccccccccccccccccaaaa
abaaaaccaaaaaccccaaaaaccccccaaaaccccccccccccccccccccaaacccccccccccaaaaaaaaaaaaaaaaaaccccccccccccccccaaaaccccccaaacaaccccccccccccccccccccccccccccccccccccccccaaaaa
abaaacccaaaaaaaacaaaaaacccccaaaaccccccccccccccccccccaaaaacccccccccaaaaaaaaaaaaaaaaacccccccccaaccccaaaaaacccccccaaaaaccccaaccccccccccccccacccccccccccccccccccaaaaa
abaaacccccaaaaaccccaaaaccccccaaacccccccccccccccccccaaaaaccccccccccaaaaaacacaaaaaacccccccccccaaccccaaaaacccccccccaaaaaaccaaaaaaccccccccccaaaccccacccccccccccaaaaaa
abaacccccaaaaaccccaacccccccccccccccaaaaacccccccccccaaaaacccccccccaaaaaaaaccaaaaaaacccccccaaaaaaaaccaaaaacccccccaaaaaaaccaaaaacccccccccccaaacccaaaccccccccccccccaa
abaaacccaaacaacccccccccccccccccccccaaaaaccccccccccccaaaaacccccccaaaaaaaaaccaaccaaacccccccaaaaaaaaccaacccccccccaaaaaaccaaaaaaccccccccccccaaaacaaaaccccccccccccccaa
abaaacccccccaaccccccccccccccccccccaaaaaaccccccccccccaaccccccaacccaaaccaaaaccccccaacccccccccaaaacccccccccccccccaacaaaccaaaaaaaccccccccccccajjjjjjjcccccccccccccccc
abcaacccccccccccccccccccccccccccccaaaaaaccccccccccccccccccccaaaaccccccaaaaccccccccccccccaacaaaaaccccccccccccccccccaaccccaaaaaacccccccccccjjjjjjjjjcccccaaaccccccc
abccccccccccccccccccccccccccccccccaaaaaaccaaccccccccccccccaaaaaacccccccaaacccccccccccaacaaaaaaaaccccccccccccccccccccccccaaccaaccccccccaiijjjjojjjjcccccaaacaccccc
abcccccccccccccccccccccccaaacccccccaaacacaaacccccccccccccccaaaaccccaaccccccccccccccccaaaaaaacccaccccccccccccccccccccccccaacccccccccccaiiijjooooojjkccaaaaaaaacccc
abccccccccccccccccccccccaaaaccccccccccaaaaaccccccccccccccccaaaaacccaaaaaccccccccccccccaaaaaacccccccccccccccccccccccccccccccccccccciiiiiiiioooooookkkcaaaaaaaacccc
abccccccccccccccccccccccaaaaccccccccccaaaaaaaacccccccccccccaacaaccaaaaacccccccaaacccaaaaaaaaccccccccccccccccccccccccccccccccccchiiiiiiiiooooouoookkkccaaaaaaccccc
abcccccccccaaccccccccccccaaaccccccccccccaaaaacccccccccccccccccccccaaaaaccccccaaaacccaaaaacaacccccccccccccaacaacccccccccccccccchhhiiiinnnooouuuuoookkkccaaaaaccccc
abcccccccccaaacccccccccccccccccccccccccaaaaacccccccccccccccccccccccaaaaacccccaaaaccccccaaccccccccccccccccaaaaacccccccccccccccchhhnnnnnnnnouuuuuuppkkkkaaaaaaccccc
abccccccaaaaaaaacccaaccccccccccccccccccaacaaccaacaaccccccccccccccccaacccccccccaaaccccccaacccccccccccccccaaaaacccccccccccccccchhhnnnnnnnnntuuxuuupppkkkkkacccccccc
abccccccaaaaaaaacacaaaacccccccccccccccccccaaccaaaaacccccccccccccccccccccccccccccccccccccccccccccccccccccaaaaaacccccccaacccccchhhnnnnttttttuxxxuuppppkkkkkcccccccc
abcccccccaaaaaaccaaaaaaccccccccccaaccccccccccaaaaaccccccccccccccccccccccccaaacccccccccccccccccccccccccccccaaaaccaaccaaacccaaahhhnnntttttttuxxxxuupppppllllccccccc
abcccccccaaaaaacccaaaacccccccccaaaaaaccccccccaaaaaacccccccccccccccccccccccaaacccccccccccccccccccccccccccccacccccaaaaaaacaaaaahhhppntttxxxxxxxxuuuuvpppplllccccccc
abcccccccaaaaaacccaaaacccccccccaaaaaacccccaaaaaaaaaccccccccccccccccccccaaaaaaaacccccccccccccccccccccaaaccccccaacaaaaaaccaaaaahhhpppttxxxxxxxxyuuvvvvvppplllcccccc
abcccccccaaccaacccaacaccaaaaccccaaaaacccccaaaaaaaaaccccccccccccccccccccaaaaaaaacccccccccccccccccccccaaacaaaaaaaccaaaaaaaaaaaaahhppptttxxxxxxyyyyyyvvvppplllcccccc
SbccccccccccccccccccccccaaaacccaaaaacccccccaaaaaaaaacaaaccccccccaacccccccaaaaaccccccccaaaaacccccccccaaaaaaaaaaaaaaaaaaaaaaaaacgggpppttxxxxEzzyyyyyvvvqqqlllcccccc
abccccccccccccccccccccccaaaacccaaaaacccccccaaaaaaaaccaaaccccccccaaacaaccaaaaaaccccccccaaaaacccccccaaaaaaaaaaaaaaaaaaaaaaaaaaacgggpppsssxxxyyyyyyvvvvvqqlllccccccc
abcccaaaccccccccccccccccaaaccccccccccccccccaaaaaaaaaaaaaccccccccaaaaaaccaaaaaacccccccaaaaaacccaaaccaaaaaccaaaaaaaaaaaacccccccccgggppssswwyyyyyyvvvvqqqqlllccccccc
abcaaaaaccccccccccccccccccccccccccccccccccaaaaaaaaaaaaacccccccaaaaaaacccaccaaacccccccaaaaaacccaaacccaaaaaaaaaaaccccaaacccaaaaacgggppsswwwyyyyyyvvqqqqqlllcccccccc
abcaaaaaaccccccccccccccccccccccccccccccccccaaccaaaaaaaaaaaccccaaaaaaacccccccccccccccccaaaaacccaaacaaaacaaaaaaaaccccaaacccaaaaacggpppsswwwywwyyyvvqqqmmmlccccccccc
abcaaaaaacccccccaacaaccccccccccccccccccccccccccaaaaaaaaaaaccccccaaaaacccccccccccccccccaaaccaaaaaaaaaaacccccccaacccccccccaaaaaacggpppsswwwwwwwwyvvqqqmmmcccccccccc
abcaaaaaccccccccaaaaaccccccccccccccccccccccccccccaaaaaaaacccccccaacaaacccccccccccccccccccccaaaaaaaaaccccccccccccccccccccaaaaaagggoossswwwwrrwwwvvqqmmmccccccccccc
abcaaaaacccccccaaaaaccccccccccccccccccccccccccccaaaaaaacccccccccaaccccccccccccccccccccccccccaaaaaaacccccccccccaaaccccccccaaaaagggooosssssrrrrwwwvqqmmmcccaacccccc
abcccccccccccccaaaaaaccccccccccccccccccccaacccccccccaaaccccccccccccccccccccccccccccccccccccccaaaaaaccccccccccccaaaaccccccaaaccgggooosssssrrrrrwwrrqmmmcccaacccccc
abcccccccccccccccaaaacccccccccccccccccccaaaacccccccacaaacccccccccccccccccccccccccccccccccccccaaaaaaacccccccccaaaaaacccccccccccgffoooooosoonrrrrrrrrmmmccaaaaacccc
abcccccccccccccccaccccccccccccccccccccccaaaacccccccaaaaacccccccccccccccccccccccccccccccccccccaaacaaacccccccccaaaaacccccccccccccfffoooooooonnnrrrrrmmmddcaaaaacccc
abccccccccccccccccccccccccccccccccccccccaaaaccccccccaaaaacccccccccccccccccccccccccaaaccccccccaacccccccccccccccaaaaaccccccccccccffffoooooonnnnnnrnnmmmdddaaaaacccc
abcccccccccccccccccccccccccccccccccccccccccccccccccaaaaaacccccccccccccccccaaaaaccaaaacccccccccccccccccccccccccaacccccccccccccccfffffffffeeeennnnnnmmdddaaaacccccc
abcccccccaaaccccccccaccccccccccccccccccccccccccccccaaaaccccccccccccaaaccccaaaaaccaaaaccccccccccccccccccccccccccccccccccccccccccccfffffffeeeeennnnnmddddaaaaaccccc
abcccaaccaaacccccaaaacccccaacccccccccccccccccccccccccaaacccccccccccaaacccaaaaaacccaaaccccccccccccccccccccccccccccccccccccccccccccccffffeeeeeeeedddddddcccaacccccc
abcccaaaaaaacccccaaaaaaccaaacccccccccccccccccccccccccccacccccccccccaaaaccaaaaaaccccccccccccccccccccccccccaacccccccccaaaccccccccccccccaaaaaaeeeeedddddcccccccccccc
abcccaaaaaacccccccaaaacccaaacaaaccccaaaacccccccccaaacaaaccccccaacccaaaacaaaaaaacccccccccccccccccccccccccaaaccccccccaaaacccccccccccccccccccaaaaeeddddccccccccccccc
abccccaaaaaaaacccaaaaaaaaaaaaaaaccccaaaacccccccccaaaaaaacccccaaacccaaaaaaaaaacccccccccccccccccccccccaaacaaaccccccccaaaaccccccccccccccccccccaaaccccccccccccccaaaca
abcccaaaaaaaaacccaacaaaaaaaaaaacccccaaaaccccccccccaaaaaacaaacaaacaaaaaaaaaacccccccccccccccaaacccccccaaaaaaaaaaccccccaaaccccccccccccccccccccaacccccccccccccccaaaaa
abcccaaaaaaaacccccccccccaaaaaacccccccaacccccccccccaaaaaaaaaaaaaaaaaaaaaaaaccccccccccccccccaaaacccccccaaaaaaaaacccccccccccccccccccccccccccccaaacccccccccccccccaaaa
abccaaaaaaacccccccccccccaaaaaaacccccccccccccccccaaaaaaaaaaaaaaaaaaaaaaaaaaacccccccccccccccaaaacccccccaaaaaaaacccccccccccccccccccccccccccccccccccccccccccccccaaaaa
\ No newline at end of file

M src/aoc2022.gleam => src/aoc2022.gleam +3 -3
@@ 1,9 1,9 @@
import gleam/io
import aoc2022/day10
import aoc2022/day12

pub fn main() {
  io.print("Part 1: ")
  io.debug(day10.part1())
  io.debug(day12.part1())
  io.print("Part 2: ")
  io.debug(day10.part2())
  io.debug(day12.part2())
}

A src/aoc2022/day12.gleam => src/aoc2022/day12.gleam +176 -0
@@ 0,0 1,176 @@
//// --- Day 12: Hill Climbing Algorithm ---
//// https://adventofcode.com/2022/day/12

import aoc2022/utils
import gleam/int
import gleam/list
import gleam/map
import gleam/option.{None, Option, Some}
import gleam/queue
import gleam/result
import gleam/string

type Pos {
  Pos(x: Int, y: Int)
}

type HeightMap =
  map.Map(Pos, Int)

type Visited =
  map.Map(Pos, Option(Pos))

type Queue =
  queue.Queue(Pos)

type State {
  State(end: Pos, height_map: HeightMap, queue: Queue, visited: Visited)
}

pub fn part1() {
  let #(start, end, height_map) = parse_input()
  play(start, end, height_map)
}

pub fn part2() {
  let #(_start, end, height_map) = parse_input()

  height_map
  |> map.filter(fn(_pos, height) { height == 0 })
  |> map.keys
  |> list.map(play(_, end, height_map))
  |> list.filter(result.is_ok)
  |> list.map(utils.assert_ok)
  |> list.reduce(int.min)
}

fn play(start, end, height_map) {
  let visited = map.from_list([#(start, None)])
  let queue = queue.from_list([start])
  let state = State(end, height_map, queue, visited)
  do_play(state)
}

// BFS to find path from start to end
// Could be improved by A* but not really required for this one
fn do_play(state: State) -> Result(Int, Nil) {
  case queue.pop_front(state.queue) {
    Ok(#(current_pos, queue)) ->
      case current_pos == state.end {
        True -> {
          let path = unravel_path(state.visited, current_pos)
          Ok(list.length(path) - 1)
        }
        False -> {
          let next =
            next_positions(current_pos, state.height_map, state.visited)
          let visited = add_visited(state.visited, current_pos, next)
          let queue = add_queue(queue, next)
          let state = State(state.end, state.height_map, queue, visited)
          do_play(state)
        }
      }
    // Queue depleted, no path found
    Error(Nil) -> Error(Nil)
  }
}

fn unravel_path(visited, end_pos) -> List(Pos) {
  do_unravel_path(visited, end_pos, [])
}

fn do_unravel_path(visited, pos, path) -> List(Pos) {
  case utils.assert_ok(map.get(visited, pos)) {
    Some(prev_pos) -> do_unravel_path(visited, prev_pos, [pos, ..path])
    None -> [pos, ..path]
  }
}

fn add_visited(visited: Visited, current_pos: Pos, next: List(Pos)) {
  list.fold(
    next,
    visited,
    fn(visited, next_pos) { map.insert(visited, next_pos, Some(current_pos)) },
  )
}

fn add_queue(queue: Queue, next: List(Pos)) {
  list.fold(
    next,
    queue,
    fn(queue, next_pos) { queue.push_back(queue, next_pos) },
  )
}

fn next_positions(pos, height_map, visited) -> List(Pos) {
  pos
  |> neighbours()
  // Remove out of bounds
  |> list.filter(map.has_key(height_map, _))
  // Remove visited
  |> list.filter(fn(neighbour) { !map.has_key(visited, neighbour) })
  // Remove neighbours too high to climb
  |> list.filter(fn(neighbour) {
    assert Ok(pos_height) = map.get(height_map, pos)
    assert Ok(neighbour_height) = map.get(height_map, neighbour)
    neighbour_height <= pos_height + 1
  })
}

fn neighbours(pos: Pos) -> List(Pos) {
  [
    Pos(pos.x + 1, pos.y),
    Pos(pos.x - 1, pos.y),
    Pos(pos.x, pos.y + 1),
    Pos(pos.x, pos.y - 1),
  ]
}

// -----------------------------------------------------------------------------
// Input Parsing
// -----------------------------------------------------------------------------

fn parse_input() -> #(Pos, Pos, HeightMap) {
  let char_map =
    "day12.txt"
    |> utils.read_input
    |> parse_to_char_map

  let start = find_char(char_map, "S")
  let end = find_char(char_map, "E")
  let height_map = map.map_values(char_map, fn(_, v) { char_to_height(v) })
  #(start, end, height_map)
}

fn char_to_height(char) {
  let char = case char {
    "S" -> "a"
    "E" -> "z"
    other -> other
  }

  utils.ord(char) - 97
}

fn find_char(map, char) -> Pos {
  assert [#(pos, _)] =
    map
    |> map.filter(fn(_, v) { v == char })
    |> map.to_list

  pos
}

fn parse_to_char_map(input) {
  input
  |> parse_to_nested_list
  |> list.flatten
  |> map.from_list
}

fn parse_to_nested_list(input) {
  let rows = string.split(input, "\n")
  use y, row <- list.index_map(rows)
  use x, char <- list.index_map(string.to_graphemes(row))
  #(Pos(x, y), char)
}

M src/aoc2022/utils.gleam => src/aoc2022/utils.gleam +7 -0
@@ 1,4 1,5 @@
import gleam/int
import gleam/io
import gleam/list

// Externals


@@ 52,3 53,9 @@ pub fn list_at(list: List(a), at: Int) -> Result(a, Nil) {
    False -> Error(Nil)
  }
}

pub fn inspect(term: anything, label: String) -> anything {
  io.print(label)
  io.print(": ")
  io.debug(term)
}