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

Advent of Code - Day 7

2024/07.livemd

Advent of Code - Day 7

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

Problem

https://adventofcode.com/2024/day/7

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2024", "7", System.fetch_env!("LB_AOC_SESSION"))
example_input = "190: 10 19
3267: 81 40 27
83: 17 5
156: 15 6
7290: 6 8 6 15
161011: 16 10 13
192: 17 8 14
21037: 9 7 18 13
292: 11 6 16 20"
defmodule Day7 do
  def parse(str) do 
    str
      |> String.split("\n", trim: true)
      |> Enum.map(fn eq ->
        [x, nums] = String.split(eq, ":", limit: 2, trim: true)
        parsed_nums = String.split(nums, " ", trim: true) |> Enum.map(&String.to_integer(&1))
        {String.to_integer(x), parsed_nums}
    end)
  end

  def op(nil, b, _), do: b
  def op(a, b, "0"), do: a + b
  def op(a, b, "1"), do: a * b
  def op(a, b, "2"), do: String.to_integer("#{a}#{b}")

  def solvable?({goal, nums}, base) do
    len = length(nums) - 1
    max = String.to_integer(String.duplicate("#{base-1}", len), base)
    nums_with_index = nums |> Enum.with_index()

    Enum.any?(0..max, fn i ->
      op_combo = String.pad_leading(Integer.to_string(i, base), len, "0") |> String.graphemes()

      goal == Enum.reduce_while(nums_with_index, nil, fn {num, idx}, acc ->
        if acc == nil || acc <= goal do
          {:cont, op(acc, num, Enum.at(op_combo, idx - 1)) }
        else
          {:halt, nil }
        end
      end)
    end)
  end

  def async_solve(problem, base) do
    Task.async_stream(problem, fn {goal, nums} ->
      if solvable?({goal, nums}, base) do
        goal
      else
        0
      end
    end)
    |> Enum.to_list()
    |> Enum.reduce(0, fn {:ok, x}, acc -> acc + x end)
  end

  def solve_part1(str) do
    parse(str) |> async_solve(2)
  end

  def solve_part2(str) do
    parse(str) |> async_solve(3)
  end
end
Day7.solve_part1(example_input)
Day7.solve_part1(puzzle_input)
Day7.solve_part2(example_input)
Day7.solve_part2(puzzle_input)