rvnash

rvnash

AOC 2025 is right around the corner. Last year, my first year, I just started a raw Elixir application and manually created modules for each day’s challenge. This year I’m thinking of starting with the aoc package. Anyone have opinions about what the most helpful framework is for enjoying this year’s challenges?

Showing Posts 1 to 7

adamu

adamu

Here’s a similar thread, which also contains my opinion :grin:

lud

lud

The aoc package is opinionated, it creates modules with the parse/2, part_one/1 and part_two/2 functions, and it creates tests if you fancy some TDD. If you do not like that, you can still use it just to fetch the inputs locally.

If you want to do your setup yourself I guess starting with a bash script that generates a module can be enough.

I’d suggest to try aoc right now, see the Are you trying this library before December 1st ? section so you know if it is useful to you before day 1.

This is my bash command to work with my package that I run every morning:

play:
  mix aoc.create | rg '\.ex' | rg -v Compiling | xargs code
  mix format
  nohup firefox $(mix aoc.url | rg "https") >/dev/null 2>&1 &
  echo 'Good Luck :)'
vkryukov

vkryukov

Frankly, with LLMs these days, you don’t necessarily need to rely on external packages for simple things - you can just ask the LLM of your choice to write your own little modules (or even write them yourself!).

My approach: 1) a module to download and cache the input files, called like this: AOC.input!(1, 2015) (first call downloads, consequtive calls read from the disk), 2) two simple templates called lib/y2015/day00.ex and test/y2015/day00_test.exs, 3) a mix tasks that would copy this tasks in appropriate places and change 00 to whatever the proper date is. So that I can modify the templates for my liking - they are just elixir files.

That’s it. That’s the whole setup.

# lib/aoc.ex
defmodule AOC do
  @year 2025
  @ua "Victor Kryukov (victor.kryukov@gmail.com; Elixir/Req)"
  @input_dir "priv/aoc"

  def session! do
    ".aoc_session"
    |> File.read!()
    |> String.trim()
  end

  def client(session \\ session!()) do
    Req.new(
      base_url: "https://adventofcode.com",
      headers: [
        {"cookie", "session=#{session}"},
        {"user-agent", @ua}
      ],
      redirect: true
    )
  end

  def input!(day, year \\ @year) do
    dir = @input_dir
    path = input_path(day, year, dir)

    cond do
      File.exists?(path) -> read_input_file!(path)
      true -> fetch_and_store_input!(day, year, path, dir)
    end
  end

  def save_input!(day, dir \\ @input_dir, year \\ @year) do
    path = input_path(day, year, dir)
    File.mkdir_p!(dir)
    File.write!(path, input!(day, year) <> "\n")
    path
  end

  defp fetch_and_store_input!(day, year, path, dir) do
    body = fetch_remote_input!(day, year)
    File.mkdir_p!(dir)
    File.write!(path, body <> "\n")
    body
  end

  defp fetch_remote_input!(day, year) do
    remote_path = "/#{year}/day/#{day}/input"

    case Req.get(client(), url: remote_path) do
      {:ok, %{status: 200, body: body}} -> String.trim_trailing(body)
      {:ok, %{status: 400}} -> raise "Bad request (likely missing/invalid session)"
      {:ok, %{status: 404}} -> raise "Not available yet (unlocks midnight ET Dec 1–25)"
      {:ok, resp} -> raise "HTTP #{resp.status} for #{remote_path}"
      {:error, err} -> raise err
    end
  end

  defp input_path(day, year, dir) do
    name = "#{year}_day#{day |> Integer.to_string() |> String.pad_leading(2, "0")}.txt"
    Path.join(dir, name)
  end

  defp read_input_file!(path) do
    path
    |> File.read!()
    |> String.trim_trailing()
  end
end
# lib/y2015/day00.ex
defmodule Y2015.Day00 do
  def part1(_s) do
  end

  def part2(_s) do
  end
end
# test/y2015/day00_test.exs
defmodule Y2015.Day00Test do
  use ExUnit.Case

  import Y2015.Day00

  @tag :skip
  test "part1/1" do
    assert part1(AOC.input!(0, 2015))
  end

  @tag :skip
  test "part2/1" do
    assert part2(AOC.input!(0, 2015))
  end
end
# mix/tasks/aoc.new.ex
defmodule Mix.Tasks.Aoc.New do
  use Mix.Task

  @shortdoc "Generate scaffolding for a new Advent of Code day"
  @moduledoc """
  Creates solution and test files for the given `year` and `day` by copying the
  `y2015/day00` templates and updating the placeholders.

  ## Examples

      mix new 2018 7
  """

  @impl Mix.Task
  def run([year_str, day_str]) do
    with {year, ""} <- Integer.parse(year_str),
         {day, ""} <- Integer.parse(day_str) do
      ensure_bounds!(year, day)

      day_padded = day |> Integer.to_string() |> String.pad_leading(2, "0")

      lib_target = Path.join(["lib", "y#{year}", "day#{day_padded}.ex"])
      test_target = Path.join(["test", "y#{year}", "day#{day_padded}_test.exs"])

      ensure_not_exists!(lib_target)
      ensure_not_exists!(test_target)

      copy_template!("lib/y2015/day00.ex", lib_target, year, day, day_padded)
      copy_template!("test/y2015/day00_test.exs", test_target, year, day, day_padded)

      Mix.shell().info([
        "Generated ",
        lib_target,
        " and ",
        test_target
      ])
    else
      _ ->
        Mix.raise("YEAR and DAY must be integers")
    end
  end

  def run(_args) do
    Mix.raise("Usage: mix new YEAR DAY")
  end

  defp ensure_bounds!(year, day) when year in 1_900..3_000 and day in 0..25, do: :ok

  defp ensure_bounds!(_year, _day) do
    Mix.raise("YEAR must be reasonable and DAY must be between 0 and 25")
  end

  defp ensure_not_exists!(path) do
    if File.exists?(path), do: Mix.raise("#{path} already exists")
  end

  defp copy_template!(source, target, year, day, day_padded) do
    source_content = File.read!(source)
    rendered = render(source_content, year, day, day_padded)

    target |> Path.dirname() |> File.mkdir_p!()
    File.write!(target, rendered)
  end

  defp render(content, year, day, day_padded) do
    content
    |> String.replace("Y2015", "Y#{year}")
    |> String.replace("2015", Integer.to_string(year))
    |> String.replace("Day00", "Day#{day_padded}")
    |> String.replace("day00", "day#{day_padded}")
    |> String.replace("input!(0, #{year})", "input!(#{day}, #{year})")
  end
end
adamu

adamu

Note that AoC recommends that you don’t use LLMs. Here’s mine:

  1. copy dayN.exs to relevant date
  2. replace REPLACE_ME with relevant date
  3. right click and save input file from website you’re reading already
  4. run with elixir script.exs input

That’s it. That’s the whole setup. :upside_down_face:

vkryukov

vkryukov

Of course - I never use LLMs for solving the problems themselves, just to write the automation around fetching the problems etc.

sevenseacat

sevenseacat

Author of Ash Framework

I have one repo for alllllll of my puzzle solutions that I keep building in every year - GitHub - sevenseacat/advent_of_code: My Elixir solutions to Advent of Code · GitHub (spoilers: includes solutions for 457 stars and counting)

My daily process is pretty simple -

  • I run mix day <year> <day> eg. mix day 2025 1 to generate a skeleton module/test module for the day’s solution (which will be in lib/y2025/day01.ex and test/y2025/day01_test.exs)
  • I manually download the puzzle input for that day and put it in lib/y2025/input/day01.txt
  • And away I go! Now I can run code like Y2025.Day01.part1() in iex to test out my part 1 solution, etc.
al2o3cr

al2o3cr

I keep it super-minimal; each day is a subdirectory with part1.exs and part2.exs files (and inputs) and I run code with elixir part1.exs etc

Usually that file defines a module, then has top-level code afterwards that uses the module.

Some of the problems can be solved with literally just a chain of pipes, no module required.

— All posts loaded —

Where Next? Top

Trending in Challenges Top

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews