Advent of Code 2024 - Day 25

This wraps up Advent of Code 2024 for me. Looking back on the puzzles, here are the ones I found to be the hardest:

The most fun puzzles for me were:

It turns out that three of the puzzles are on both lists.

5 Likes

Yay, 50 stars. My first year trying my hand at AOC. 23 successes, and 2 times I relied on you guys to help me out. Thanks all, it was fun.

Any observations on doing this in Elixir vs. other languages? I noticed the most elegant solutions posted here were a tight sequence of transformers taking the input data through Lists, Maps, MapSets, recursive traversals or searches, and composing it all back to the required output. I know other languages have these parts, but often I found myself smiling in satisfaction at the solutions you all posted.

Happy Holidays

3 Likes

I did this in F# and if I had to do it in Elixir, it would have been almost identical in code, with some strong typing and point free pipes.

This is the first year where I have done more solution in a non-Elixir programming language and loved it. I will actually write a post about my experiences.

2 Likes

Wooooooo, bittersweet that there is no part 2 but really happy to have finished :smile:

After all the challenges involving bit operations, I realised the keys and locks can be encoded as a single integer and a bitwise AND will check if there are any overlaps.

1 Like

I got lazy for this one and just used the 2D grid module again :smiley:

defmodule AdventOfCode.Solutions.Y24.Day25 do
  alias AdventOfCode.Grid
  alias AoC.Input

  def parse(input, _part) do
    input
    |> Input.read!()
    |> String.trim()
    |> String.split("\n\n")
    |> Enum.map(&parse_block/1)
    |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
  end

  defp parse_block("#####\n" <> _ = lock), do: {:locks, parse_lock(lock)}
  defp parse_block(".....\n" <> _ = key), do: {:keys, parse_key(key)}

  defp parse_lock(lines) do
    coords = to_coords(lines)
    Enum.map(0..4, fn x -> Enum.max(Map.fetch!(coords, x)) end)
  end

  defp parse_key(lines) do
    coords = to_coords(lines)
    Enum.map(0..4, fn x -> 6 - Enum.min(Map.fetch!(coords, x)) end)
  end

  defp to_coords(lines) do
    lines
    |> String.split("\n")
    |> Grid.parse_lines(fn
      ?# -> {:ok, true}
      ?. -> :ignore
    end)
    |> elem(0)
    |> Map.keys()
    |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
  end

  def part_one(%{locks: locks, keys: keys}) do
    for lock <- locks, key <- keys, reduce: 0 do
      acc -> if opens?(lock, key), do: acc + 1, else: acc
    end
  end

  defp opens?([p | lock], [k | key]) when p + k < 6, do: opens?(lock, key)
  defp opens?([_ | _], [_ | _]), do: false
  defp opens?([], []), do: true
end