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

Day 15

day15.livemd

Day 15

Mix.install([
  {:kino, "~> 0.7.0"}
])

IEx.Helpers.c("/Users/johnb/dev/2023adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input

# Note: when making the next template, something like this works well:
#   `cat day04.livemd | sed 's/03/04/' > day04.livemd`
#

Installation and Data

input_p1example = Kino.Input.textarea("Example Data")
input_p1puzzleInput = Kino.Input.textarea("Puzzle Input")
input_source_select =
  Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
p1data = fn ->
  (Kino.Input.read(input_source_select) == :example &&
     Kino.Input.read(input_p1example)) ||
    Kino.Input.read(input_p1puzzleInput)
end

Part 1

defmodule Day15 do
  def hash(step) do
    # Increase the current value by the ASCII code you just determined.
    # Set the current value to itself multiplied by 17.
    # Set the current value to the remainder of dividing itself by 256.
    step
    |> String.codepoints()
    |> Enum.reduce(0, fn char, acc ->
      rem((acc + :binary.first(char)) * 17, 256)
      # |> IO.inspect()
    end)

    # |> IO.inspect(label: "HASH")
  end

  def solve(text) do
    text
    |> String.split(",", trim: true)
    |> Enum.map(&hash/1)
    |> Enum.sum()
  end

  def empty_boxes do
    0..255
    |> Enum.reduce(%{}, fn box_id, acc -> Map.put(acc, box_id, []) end)
  end

  def solve2(text) do
    text
    |> String.split(",", trim: true)
    |> Enum.reduce(empty_boxes(), fn step, boxes ->
      # IO.puts(step)
      if step =~ ~r/=/ do
        [label, focal_length] = String.split(step, "=", trim: true)
        focal_length = String.to_integer(focal_length)
        box_id = hash(label)

        Map.get_and_update!(boxes, box_id, fn lenses ->
          existing_item_and_index =
            lenses
            |> Enum.with_index()
            |> Enum.find(fn {{label1, _lens}, _index} -> label1 == label end)

          # IO.inspect([box_id, existing_item_and_index], label: "box_id, existing_index")
          if existing_item_and_index do
            {_item, index} = existing_item_and_index
            {lenses, List.replace_at(lenses, index, {label, focal_length})}
          else
            # store the lenses backwards - to be reversed later
            {lenses, [{label, focal_length} | lenses]}
          end

          # |> IO.inspect(label: "replacement")
        end)
        |> elem(1)
      else
        label_to_replace = String.replace(step, ~r/\-.*/, "")
        box_id = hash(label_to_replace)
        # IO.inspect([label_to_replace, box_id, boxes], label: "label_to_replace, box_id, boxes")
        Map.get_and_update!(boxes, box_id, fn lenses ->
          existing_item_and_index =
            lenses
            |> Enum.with_index()
            |> Enum.find(fn {{label, _lens}, _index} -> label == label_to_replace end)

          # IO.inspect([box_id, existing_item_and_index], label: "box_id, existing_index")
          if existing_item_and_index do
            {_item, index} = existing_item_and_index
            {lenses, List.delete_at(lenses, index)}
          else
            {lenses, lenses}
          end
        end)
        |> elem(1)
      end

      # |> IO.inspect(label: "end of loop")
    end)
    |> Enum.map(fn {box_id, list} ->
      list
      |> Enum.reverse()
      |> Enum.with_index()
      |> Enum.reduce(0, fn {{_label, focal_length}, index}, acc ->
        # IO.inspect([label, acc, index, focal_length, box_id])
        acc + (1 + index) * focal_length * (box_id + 1)
        # |> IO.inspect(label: label)
      end)
    end)
    |> Enum.sum()
  end
end

p1data.()
|> Day15.solve()
|> IO.inspect(label: "\n*** Part 1 solution (example: 1320)")

# 521434

p1data.()
|> Day15.solve2()
|> IO.inspect(label: "\n*** Part 2 solution (example: 145)")

# 248279