Skip to content

Latest commit

 

History

History
129 lines (102 loc) · 2.91 KB

d15.livemd

File metadata and controls

129 lines (102 loc) · 2.91 KB

Day 15

Solution

defmodule D15 do
  def parse_input(input) when is_binary(input) do
    input
    |> String.trim()
    |> String.replace("\n", "")
    |> String.split(",")
  end

  def custom_hash(s) when is_binary(s) do
    String.to_charlist(s)
    |> Enum.reduce(0, fn code, acc ->
      rem((acc + code) * 17, 256)
    end)
  end

  def p1(input) when is_list(input), do: input |> Enum.map(&custom_hash/1) |> Enum.sum()

  def reparse(input) when is_list(input) do
    Enum.map(input, fn ins ->
      if String.ends_with?(ins, "-") do
        {:remove, String.trim_trailing(ins, "-")}
      else
        [label, focus] = String.split(ins, "=")
        {:put, label, String.to_integer(focus)}
      end
    end)
  end

  def run(instructions) when is_list(instructions) do
    Enum.reduce(instructions, %{}, fn ins, boxes ->
      box_id = custom_hash(elem(ins, 1))
      box = Map.get(boxes, box_id, [])

      case ins do
        {:put, label, focal_length} ->
          case Enum.find_index(box, fn {another_label, _} -> another_label == label end) do
            nil ->
              Map.put(boxes, box_id, [{label, focal_length} | box])

            idx when is_number(idx) ->
              box = List.replace_at(box, idx, {label, focal_length})
              Map.put(boxes, box_id, box)
          end

        {:remove, label} ->
          idx = Enum.find_index(box, fn {another_label, _} -> another_label == label end)

          if !is_nil(idx) do
            box = List.delete_at(box, idx)
            Map.put(boxes, box_id, box)
          else
            boxes
          end
      end
    end)
    |> Enum.reject(fn {_k, v} -> length(v) == 0 end)
    |> Enum.map(fn {k, v} -> {k, Enum.reverse(v)} end)
    |> Enum.into(%{})
  end

  def p2(input) do
    input
    |> reparse()
    |> run()
    |> Enum.map(fn {box_id, lenses} ->
      Enum.with_index(lenses, 1)
      |> Enum.map(fn {{_label, focal_length}, pos} -> pos * focal_length * (box_id + 1) end)
      |> Enum.sum()
    end)
    |> Enum.sum()
  end
end
{:module, D15, <<70, 79, 82, 49, 0, 0, 24, ...>>, {:p2, 1}}

Tests

ExUnit.start(autorun: false)

defmodule D08Test do
  use ExUnit.Case, async: true

  @test_input D15.parse_input("rn=1,cm-,qp=3,cm=2,qp-,pc=4,ot=9,ab=5,pc-,pc=6,ot=7")
  @input Path.join(__DIR__, "inputs/d15") |> File.read!() |> D15.parse_input()

  test "part 1 works" do
    assert D15.custom_hash("HASH") == 52
    assert D15.p1(@test_input) == 1320

    assert D15.p1(@input) == 515_974
  end

  test "part 2 works" do
    assert D15.p2(@test_input) == 145
    assert D15.p2(@input) == 265_894
  end
end

ExUnit.run()
..
Finished in 0.00 seconds (0.00s async, 0.00s sync)
2 tests, 0 failures

Randomized with seed 872467
%{excluded: 0, failures: 0, skipped: 0, total: 2}