Skip to content

Latest commit

 

History

History
227 lines (183 loc) · 4.94 KB

day_12.livemd

File metadata and controls

227 lines (183 loc) · 4.94 KB

Day 12

Mix.install([:kino])

Prep

input_text = Kino.Input.textarea("Put your data here")
input_text =
  case Kino.Input.read(input_text) do
    "" ->
      """
      Sabqponm
      abcryxxl
      accszExk
      acctuvwj
      abdefghi
      """

    input ->
      input
  end

input =
  input_text
  |> String.split("\n", trim: true)
  |> Enum.map(&String.to_charlist/1)
  |> Enum.reduce({%{}, 0}, fn row, {map, row_num} ->
    {map, _} =
      Enum.reduce(row, {map, 0}, fn value, {map, col_num} ->
        {Map.put(map, {row_num, col_num}, {value, nil}), col_num + 1}
      end)

    {map, row_num + 1}
  end)
  |> then(&elem(&1, 0))
defmodule Renderer do
  def render(input, width, height, visited, frame) do
    images =
      for x <- 0..(height - 1) do
        for y <- 0..(width - 1) do
          case Map.has_key?(visited, {x, y}) do
            true -> create_svg_visited()
            _ -> create_svg_unvisited(input, x, y)
          end
        end
      end
      |> List.flatten()

    grid = Kino.Layout.grid(images, columns: width, gap: 0)
    Kino.Frame.render(frame, grid)
    :timer.sleep(100)
  end

  defp create_svg_unvisited(input, x, y) do
    interval = 255 / 26

    code =
      case Map.get(input, {x, y}) do
        {?S, _} -> 255
        {?E, _} -> 0
        {h, _} -> round((?z - h) * interval)
      end

    """
    <svg height="100" width="100" xmlns="http://www.w3.org/2000/svg">
      <rect height="100" width="100" style="fill:rgb(#{code},#{code},#{code})" />
    </svg>
    """
    |> Kino.Image.new(:svg)
  end

  defp create_svg_visited() do
    """
    <svg height="100" width="100" xmlns="http://www.w3.org/2000/svg">
      <rect height="100" width="100" fill="red" />
    </svg>
    """
    |> Kino.Image.new(:svg)
  end
end
defmodule Day12 do
  require Logger

  def find_path(_map, {pos, {_, wc}}, {:position, end_cond}, visited, _render_options)
      when pos == end_cond do
    _visited = Map.put(visited, end_cond, wc)
  end

  def find_path(_map, {pos, {hc, wc}}, {:value, end_cond}, visited, _render_options)
      when hc == end_cond do
    _visited = Map.put(visited, pos, wc)
  end

  def find_path(_map, {_pos, {_, nil}}, _end_cond, _visited, _render_options) do
    :no_path_found
  end

  def find_path(
        map,
        {k, {_hc, wc}} = current,
        end_cond,
        visited,
        {frame, width, height} = render_options
      ) do
    map =
      map
      |> update_neighbors(current, visited)
      |> Enum.reduce(map, fn {k, v}, acc -> Map.put(acc, k, v) end)
      |> Map.delete(k)

    next =
      map
      |> Enum.min(fn {_k1, {_h1, w1}}, {_k2, {_h2, w2}} -> w1 < w2 end)

    visited = Map.put(visited, k, wc)

    if render_options do
      Renderer.render(map, width, height, visited, frame)
    end

    find_path(map, next, end_cond, visited, render_options)
  end

  def update_neighbors(map, {{x, y}, value}, visited) do
    [{x, y + 1}, {x + 1, y}, {x, y - 1}, {x - 1, y}]
    |> Enum.reject(&Map.has_key?(visited, &1))
    |> Enum.map(&update_neighbor(map, &1, value))
    |> Enum.reject(&(&1 == :no_update))
  end

  def update_neighbor(map, pos, {hc, wc}) do
    case Map.get(map, pos) do
      {h, nil} when h <= hc + 1 -> {pos, {h, wc + 1}}
      {h, w} when h <= hc + 1 and w < wc -> {pos, {h, wc + 1}}
      _ -> :no_update
    end
  end

  def get_path(visited, {x, y} = position, weight) do
    case Map.get(visited, position) do
      0 ->
        position

      _ ->
        next_pos =
          [{x, y + 1}, {x + 1, y}, {x, y - 1}, {x - 1, y}]
          |> Enum.find(&(Map.get(visited, &1) == weight - 1))

        [position | get_path(visited, next_pos, weight - 1)]
    end
  end
end
split =
  input_text
  |> String.split("\n", trim: true)

height = Enum.count(split)
width = String.length(Enum.at(split, 0))

frame = Kino.Frame.new()

Part 1

import Day12

render_options = {frame, width, height}

{start_pos, _} = Enum.find(input, fn {_k, {h, _}} -> h == ?S end)
{end_pos, _} = Enum.find(input, fn {_k, {h, _}} -> h == ?E end)
start_value = {?a, 0}

map =
  input
  |> Map.put(start_pos, start_value)
  |> Map.put(end_pos, {?z, nil})

Day12.find_path(map, {start_pos, start_value}, {:position, end_pos}, %{}, render_options)
# |> Enum.sort(fn {_,v1},{_,v2} -> v1 > v2 end)
|> Map.get(end_pos)

Part 2

{start_pos, _} = Enum.find(input, fn {_k, {h, _}} -> h == ?S end)
{end_pos, _} = Enum.find(input, fn {_k, {h, _}} -> h == ?E end)

map =
  input
  |> Map.put(start_pos, {?a, nil})
  |> Map.put(end_pos, {?z, 0})
  |> Enum.map(fn {k, {h, w}} -> {k, {?z - h, w}} end)
  |> Enum.into(%{})

Day12.find_path(map, {end_pos, start_value}, {:value, ?z - ?a}, %{}, render_options)
|> Enum.sort(fn {_, v1}, {_, v2} -> v1 > v2 end)
|> Enum.at(0)
frame = Kino.Frame.new()
Kino.Frame.render(frame, 2)