Powered by AppSignal & Oban Pro

Advent of code day 09

2025/livebooks/day-09.livemd

Advent of code day 09

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

Setup input

example = Kino.Input.textarea("Please paste your input example:")
input = Kino.Input.textarea("Please paste your real input:")

Part 01

coords =
  example
  |> Kino.Input.read()
  |> String.split(["\n", ","], trim: true)
  |> Enum.map(&String.to_integer/1)
  |> Enum.chunk_every(2)



calc_distance = fn [x1, y1], [x2, y2] ->
  abs(x1 - x2 + 1) * abs(y1 - y2 + 1)
end

distances =
  for {a, i} <- Enum.with_index(coords),
      {b, j} <- Enum.with_index(coords),
      i < j do
    {calc_distance.(a, b), {a, b}}
  end
  |> Enum.sort(:desc)
  |> Enum.take(1)
  |> then(fn [{d, _} | _] -> d end)
defmodule GridPrinter do
  def print_grid(map) do
    rows = for {row, _col} <- Map.keys(map), do: row
    cols = for {_row, col} <- Map.keys(map), do: col

    max_row = Enum.max(rows)
    max_col = Enum.max(cols)

    for row <- 0..max_row do
      line =
        for col <- 0..max_col do
          Map.get(map, {row, col}, ".")
          
        end
        |> Enum.join(" ")

      IO.puts(line)
    end
  end
end

Part 02

# learnt a lot from https://www.youtube.com/watch?v=toDrFDh7VNs 

# Build points map
points =
  coords
  |> Enum.map(fn [x, y] -> {{x, y}, 1} end)
  |> Map.new()

# Build index maps
xs_idx =
  coords
  |> Enum.map(&amp;hd/1)
  |> Enum.uniq()
  |> Enum.sort()
  |> Enum.with_index()
  |> Map.new()

ys_idx =
  coords
  |> Enum.map(&amp;Enum.at(&amp;1, 1))
  |> Enum.uniq()
  |> Enum.sort()
  |> Enum.with_index()
  |> Map.new()

# Grid
grid =
  for x <- 0..(map_size(xs_idx) * 2 - 2),
      y <- 0..(map_size(ys_idx) * 2 - 2),
      into: %{} do
    {{x, y}, 0}
  end

# Shift points (circular)
[head | rest] = coords
shifted_points = rest ++ [head]

# Draw walls
grid =
  coords
  |> Enum.zip(shifted_points)
  |> Enum.reduce(grid, fn {[x1, y1], [x2, y2]}, grid ->
    {cx1, cx2} = Enum.min_max([xs_idx[x1] * 2, xs_idx[x2] * 2])
    {cy1, cy2} = Enum.min_max([ys_idx[y1] * 2, ys_idx[y2] * 2])

    for cx <- cx1..cx2,
        cy <- cy1..cy2,
        reduce: grid do
      acc -> Map.put(acc, {cx, cy}, 1)
    end
  end)

# Flood fill
outside = MapSet.new([{-1, -1}])
queue = [{-1, -1}]

max_x = map_size(xs_idx) * 2 - 2
max_y = map_size(ys_idx) * 2 - 2

outside =
  Stream.iterate(0, &amp;(&amp;1 + 1))
  |> Enum.reduce_while({queue, grid, outside}, fn _, {queue, grid, outside} ->
    case queue do
      [] ->
        {:halt, outside}

      [{tx, ty} | rest] ->
        neighbors = [{tx - 1, ty}, {tx + 1, ty}, {tx, ty - 1}, {tx, ty + 1}]

        {queue, outside} =
          Enum.reduce(neighbors, {rest, outside}, fn {nx, ny}, {queue, outside} ->
            out_of_bound = nx < -1 or ny < -1 or nx > max_x + 1 or ny > max_y + 1
            is_wall = Map.get(grid, {nx, ny}, 0) == 1
            visited = MapSet.member?(outside, {nx, ny})

            if not out_of_bound and not is_wall and not visited do
              {[{nx, ny} | queue], MapSet.put(outside, {nx, ny})}
            else
              {queue, outside}
            end
          end)

        {:cont, {queue, grid, outside}}
    end
  end)

# since we now each one is outside we can build know the insiders 
filled =
  Enum.reduce(grid, grid, fn {k, _v}, g ->
    if not MapSet.member?(outside, k) do
      Map.put(g, k, 1)
    else
      g
    end
  end)

# here we are computing every sum of ones to a giving point
psa =
  Enum.sort_by(filled, fn {{x, y}, _} -> {x, y} end)
  |> Enum.reduce(%{}, fn {{x, y} = k, v}, psa ->
    left = if x > 0, do: Map.get(psa, {x - 1, y}, 0), else: 0
    top = if y > 0, do: Map.get(psa, {x, y - 1}, 0), else: 0
    topleft = if x > 0 and y > 0, do: Map.get(psa, {x - 1, y - 1}, 0), else: 0

    value = left + top - topleft + v

    Map.put(psa, k, value)
  end)

valid? = fn {x1, y1}, {x2, y2} ->
  {cx1, cx2} = Enum.min_max([xs_idx[x1] * 2, xs_idx[x2] * 2])
  {cy1, cy2} = Enum.min_max([ys_idx[y1] * 2, ys_idx[y2] * 2])

  left = Map.get(psa, {cx1 - 1, cy2}, 0)

  top = Map.get(psa, {cx2, cy1 - 1}, 0)

  topleft =
    Map.get(psa, {cx1 - 1, cy1 - 1}, 0)

  value = Map.get(psa, {cx2, cy2}, 0)

  count = value - left - top + topleft
  count == (cx2 - cx1 + 1) * (cy2 - cy1 + 1)
end

result =
  for {[x1, y1], i} <- Enum.with_index(coords),
      [x2, y2] <- Enum.take(coords, i),
      valid?.({x1, y1}, {x2, y2}),
      reduce: 0 do
    acc ->
      max(acc, (abs(x1 - x2) + 1) * (abs(y1 - y2) + 1))
  end

# 1530527040