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

Day6

2023/elixir/day6.livemd

Day6

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"},
  {:benchee, "~> 1.0"}
])

Get Input

{:ok, data} = KinoAOC.download_puzzle("2023", "6", System.fetch_env!("LB_AOC_SECRET"))

Solve

defmodule Day6 do
  def parse1(data) do
    data
    |> String.split("\n", trim: true)
    |> Enum.map(fn row ->
      Regex.scan(~r/\d+/, row) |> List.flatten() |> Enum.map(&String.to_integer/1)
    end)
    |> Enum.zip()
  end

  def parse2(data) do
    data
    |> String.split("\n", trim: true)
    |> Enum.map(fn row ->
      Regex.scan(~r/\d+/, row) |> Enum.join() |> String.to_integer()
    end)
  end

  def task1(pairs) do
    pairs
    |> Enum.map(&do_race/1)
    |> Enum.reduce(1, fn x, acc -> x * acc end)
  end

  def task2([t, d]), do: calc_race_res({t, d})

  # from two initial equations:
  #   a + b = c
  #   a * b = d
  # we make:
  #   -a^2 + ac - d > 0
  # or:
  #   -a^2 + ac - d+1 = 0
  def calc_race_res({t, d}) do
    {:ok, {x1, x2}} = quad(-1, t, -1 * (d + 1))
    [a, b] = Enum.sort([ceil(x1), floor(x2)])
    b - a + 1
  end

  # slow solution, but still works for T2
  def do_race({t, d}) do
    # 0 and t doesn't make sense
    1..(t - 1)
    |> Enum.reduce({0, t, d}, fn th, {cnt, ts, ds} ->
      tt = ts - th
      d = tt * th
      (d > ds && {cnt + 1, ts, ds}) || {cnt, ts, ds}
    end)
    |> elem(0)
  end

  def quad(a, b, c) do
    d = :math.pow(b, 2) - 4 * a * c

    if d >= 0 do
      x1 = (-1 * b + :math.sqrt(d)) / (2 * a)
      x2 = (-1 * b - :math.sqrt(d)) / (2 * a)
      {:ok, {x1, x2}}
    else
      {:error, [error: "Discriminant less than zero!"]}
    end
  end

  def out(res, t), do: IO.puts("Res #{t}: #{res}")
end

dt = """
Time:      7  15   30
Distance:  9  40  200
"""

t1 = fn inp -> inp |> Day6.parse1() |> Day6.task1() end
t2 = fn inp -> inp |> Day6.parse2() |> Day6.task2() end

t1.(dt) |> Day6.out("task1-test")
t2.(dt) |> Day6.out("task2-test")
t1.(data) |> Day6.out("task1")
t2.(data) |> Day6.out("task2")

Benchee.run(
  %{
    "day_6_part1" => t1,
    "day_6_part2" => t2
  },
  inputs: %{"test" => dt, "full" => data}
)

:noop