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

--- Day 8: Resonant Collinearity ---

2024/day_8.livemd

— Day 8: Resonant Collinearity —

Mix.install([{:kino_aoc, "~> 0.1"}, {:kino_explorer, "~> 0.1.20"}])

Setup

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2024", "8", System.fetch_env!("LB_AOC_SESSION_COOKIE"))
input = Kino.Input.textarea("input")
defmodule ResonantCollinearity do
  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&String.graphemes/1)
    |> Enum.with_index()
    |> Enum.reduce(%{}, fn {row, row_i}, acc ->
      for {point, col_i} <- Enum.with_index(row), point != ".", reduce: acc do
        acc -> Map.put(acc, {row_i, col_i}, point)
      end
    end)
  end

  def max_bounds(input) do
    input
    |> String.split("\n", trim: true)
    |> length()
    |> Kernel.-(1)
  end

  def maybe_add_to_set(set, node, max) do
    if in_bounds?(node, max), do: MapSet.put(set, node), else: set
  end

  def in_bounds?({row, col}, max) do
    row in 0..max and col in 0..max
  end

  def map_tl(map) do
    with [hd | _tl] <- Map.keys(map) do
      Map.delete(map, hd)
    end
  end

  # a bottom right b top left
  def nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff}) when ra >= rb and ca >= cb do
    [{ra + r_diff, ca + c_diff}, {rb - r_diff, cb - c_diff}]
  end

  # a bottom left b top right
  def nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff}) when ra >= rb and ca <= cb do
    [{ra + r_diff, ca - c_diff}, {rb - r_diff, cb + c_diff}]
  end

  # a top right b bottom left
  def nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff}) when ra <= rb and ca >= cb do
    [{ra - r_diff, ca + c_diff}, {rb + r_diff, cb - c_diff}]
  end

  # a top left b bottom right
  def nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff}) when ra <= rb and ca <= cb do
    [{ra - r_diff, ca - c_diff}, {rb + r_diff, cb + c_diff}]
  end
end

Part 1

import ResonantCollinearity

test = Kino.Input.read(input)

grid = parse(puzzle_input)
max = max_bounds(puzzle_input)

for {{ra, ca}, pa} <- grid, reduce: MapSet.new() do
  set ->
    for {{rb, cb}, pb} <- grid, pa == pb, {ra, ca} != {rb, cb}, reduce: set do
      set ->
        r_diff = abs(ra - rb)
        c_diff = abs(ca - cb)
        [a_node, b_node] = nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff})

        set
        |> maybe_add_to_set(a_node, max)
        |> maybe_add_to_set(b_node, max)
    end
end
|> MapSet.size()

Part 2

import ResonantCollinearity

test = Kino.Input.read(input)

grid = parse(puzzle_input)
max = max_bounds(puzzle_input)

for {{ra, ca}, pa} <- grid, reduce: MapSet.new() do
  set ->
    for {{rb, cb}, pb} <- grid, pa == pb, {ra, ca} != {rb, cb}, reduce: set do
      set ->
        r_diff = abs(ra - rb)
        c_diff = abs(ca - cb)

        start_nodes = nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff})

        set =
          Enum.reduce(start_nodes ++ [{ra, ca}, {rb, cb}], set, &amp;maybe_add_to_set(&amp;2, &amp;1, max))

        Enum.reduce(0..49, {start_nodes, set}, fn _i, {prev_nodes, set} ->
          new_nodes = nodes(prev_nodes, {r_diff, c_diff})
          {new_nodes, Enum.reduce(new_nodes, set, &amp;maybe_add_to_set(&amp;2, &amp;1, max))}
        end)
        |> elem(1)
    end
end
|> MapSet.size()