Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day Eight

day8.livemd

Day Eight

Mix.install([
  {:kino, "~> 0.7.0"}
])

Section

input = Kino.Input.textarea("Input")
defmodule DayEight do
  def solve1(input) do
    input
    |> parse()
    |> find_visible()
    |> Map.values()
    |> Enum.filter(fn x -> x end)
    |> length()
  end

  def solve2(input) do
    map = parse(input)

    map
    |> Map.keys()
    |> Enum.map(&scenic_score(map, &1))
    |> Enum.max()
  end

  def print(result) do
    dx = result |> Map.keys() |> Enum.map(&elem(&1, 0)) |> Enum.max()
    dy = result |> Map.keys() |> Enum.map(&elem(&1, 1)) |> Enum.max()

    for j <- 0..dy do
      for i <- 0..dx do
        IO.write(if result[{i, j}], do: "T", else: "F")
      end

      IO.write("\n")
    end

    result
  end

  def find_visible(map) do
    dx = map |> Map.keys() |> Enum.map(&amp;elem(&amp;1, 0)) |> Enum.max()
    dy = map |> Map.keys() |> Enum.map(&amp;elem(&amp;1, 1)) |> Enum.max()

    for i <- 0..dx,
        j <- 0..dy,
        reduce: %{} do
      acc ->
        Map.put(acc, {i, j}, visible?(map, {i, j}))
    end
  end

  def edge?(map, {x, y}) do
    dx = map |> Map.keys() |> Enum.map(&amp;elem(&amp;1, 0)) |> Enum.max()
    dy = map |> Map.keys() |> Enum.map(&amp;elem(&amp;1, 1)) |> Enum.max()

    x == 0 || y == 0 || x == dx || y == dy
  end

  def visible_left?(map, {x, y}) do
    0..(x - 1)
    |> Enum.all?(fn i -> map[{i, y}] < map[{x, y}] end)
  end

  def visible_right?(map, {x, y}) do
    dx = map |> Map.keys() |> Enum.map(&amp;elem(&amp;1, 0)) |> Enum.max()

    dx..(x + 1)
    |> Enum.all?(fn i -> map[{i, y}] < map[{x, y}] end)
  end

  def visible_top?(map, {x, y}) do
    0..(y - 1)
    |> Enum.all?(fn i -> map[{x, i}] < map[{x, y}] end)
  end

  def visible_bottom?(map, {x, y}) do
    dy = map |> Map.keys() |> Enum.map(&amp;elem(&amp;1, 1)) |> Enum.max()

    dy..(y + 1)
    |> Enum.all?(fn i -> map[{x, i}] < map[{x, y}] end)
  end

  def visible?(map, pt) do
    edge?(map, pt) ||
      visible_left?(map, pt) ||
      visible_right?(map, pt) ||
      visible_top?(map, pt) ||
      visible_bottom?(map, pt)
  end

  def score_left(_, {0, _}) do
    0
  end

  def score_left(map, {x, y}) do
    (x - 1)..0
    |> Enum.reduce_while(0, fn i, acc ->
      if map[{i, y}] < map[{x, y}] do
        {:cont, acc + 1}
      else
        {:halt, acc + 1}
      end
    end)
  end

  def score_right(map, {x, y}) do
    dx = map |> Map.keys() |> Enum.map(&amp;elem(&amp;1, 0)) |> Enum.max()

    if x == dx do
      0
    else
      (x + 1)..dx
      |> Enum.reduce_while(0, fn i, acc ->
        if map[{i, y}] < map[{x, y}] do
          {:cont, acc + 1}
        else
          {:halt, acc + 1}
        end
      end)
    end
  end

  def score_up(_, {_, 0}) do
    0
  end

  def score_up(map, {x, y}) do
    (y - 1)..0
    |> Enum.reduce_while(0, fn i, acc ->
      if map[{x, i}] < map[{x, y}] do
        {:cont, acc + 1}
      else
        {:halt, acc + 1}
      end
    end)
  end

  def score_down(map, {x, y}) do
    dy = map |> Map.keys() |> Enum.map(&amp;elem(&amp;1, 1)) |> Enum.max()

    if y == dy do
      0
    else
      (y + 1)..dy
      |> Enum.reduce_while(0, fn i, acc ->
        if map[{x, i}] < map[{x, y}] do
          {:cont, acc + 1}
        else
          {:halt, acc + 1}
        end
      end)
    end
  end

  def scenic_score(map, pt) do
    score_left(map, pt) * score_right(map, pt) * score_up(map, pt) * score_down(map, pt)
  end

  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&amp;String.to_charlist/1)
    |> Enum.with_index()
    |> Enum.reduce(%{}, fn {row, i}, acc ->
      row
      |> Enum.with_index()
      |> Enum.reduce(acc, fn {c, j}, acc -> Map.put(acc, {i, j}, c - ?0) end)
    end)
  end
end

DayEight.solve2(Kino.Input.read(input))