Powered by AppSignal & Oban Pro

Day 22

day22.livemd

Day 22

# Note: when making the next template, something like this works well:
#   `cat day04.livemd | sed 's/22/04/' > day04.livemd`
# When inspecting lists of numbers, use "charlists: :as_lists"
#
Mix.install([
  # Join the string so a copy of dayN to dayM doesn't destroy it.
  {:kino, "~> 0.1" <> "4.2"}
])
import Integer
import Bitwise

# Join the string so a copy of dayN to dayM doesn't destroy it.
IEx.Helpers.c("/Users/johnb/dev/2" <> "0" <> "2" <> "4adventOfCode/advent_of_code.ex")
alias AdventOfCode, as: AOC
alias Kino.Input

Installation and Data

input_example = Kino.Input.textarea("Example Data", monospace: true)
input_puzzleInput = Kino.Input.textarea("Puzzle Input", monospace: true)
input_source_select =
  Kino.Input.select("Source", [{:example, "example"}, {:puzzle_input, "puzzle input"}])
data = fn ->
  (Kino.Input.read(input_source_select) == :example &&
     Kino.Input.read(input_example)) ||
    Kino.Input.read(input_puzzleInput)
end

Solution

defmodule Day22 do
  def text_parse(text) do
    text
    |> AOC.as_single_lines()
    |> Enum.map(&String.to_integer/1)
  end

  def mix(secret, mixin) do
    bxor(secret, mixin)
  end

  def prune(secret) do
    rem(secret, 16777216)
  end

  def evolve(secret) do
    s1 = mix(secret, secret * 64) |> prune()
    s2 = mix(s1, div(s1, 32)) |> prune()
    _s3 = mix(s2, s2 * 2048) |> prune()
    # [s1,s2,s3]
    # |> Enum.map(fn x -> Integer.to_string(x, 16) end)
    # |> AOC.inspect(label: "evolve of #{secret} (#{Integer.to_string(secret, 16)})")
    # s3
  end

  def solve1(text) do
    # Enum.reduce(1..10, 123, fn _x, acc -> evolve(acc) |> IO.inspect() end)

    text_parse(text)
    |> Enum.map(fn line -> 
      Enum.reduce(1..2000, line, fn _x, acc -> evolve(acc) end)
    end)
    |> Enum.sum()
  end

  def generate_prices(line, iterations) do
    [_final_secret, pricelist] = Enum.reduce(1..iterations, [line, [rem(line, 10)]], fn _x, [acc, prices] -> 
        next_secret = evolve(acc)
        [next_secret, prices ++ [rem(next_secret, 10)]]
      end)
    [line, pricelist]
  end

  def solve2(text) do
    # generate_prices(123, 10) |> AOC.inspect()

    # result = [1, 2, 3, 2024]
    result = text_parse(text)
    |> Enum.map(fn line -> 
      generate_prices(line, 2000)
    end)
    |> Enum.reduce(%{}, fn [initial_secret, prices], acc ->
      prices
      |> Enum.chunk_every(5, 1, :discard)
      |> Enum.reduce(acc, fn five_prices, acc1 ->
        delta4 = Enum.chunk_every(five_prices, 2, 1, :discard)
          |> Enum.map(fn [a, b] -> b - a end)
        last_price = List.last(five_prices)
        get_and_update_in(acc1, [delta4], fn current_value ->
          cond do
            is_nil(current_value) -> 
              {current_value, %{sum: last_price, found_in: MapSet.new([initial_secret])}} 
            MapSet.member?(current_value.found_in, initial_secret) ->
              {current_value, current_value}
            true ->
              {current_value, %{sum: current_value.sum + last_price, 
                found_in: MapSet.put(current_value.found_in, initial_secret) }}
          end
        end)
        |> elem(1) 
      end)
    end)

    # result[[-2,1,-1,3]]
    # |> AOC.inspect(label: "[-2,1,-1,3]")

    result
    |> Enum.sort_by(fn {_delta4, %{sum: sum, found_in: _found_in}} -> sum end, :desc)
    |> List.first()
  end
end

# Example:

IO.inspect(Time.utc_now())
data.()
|> Day22.solve1()
|> IO.inspect(label: "\n*** Part 1 solution (example: 37327623)")
IO.inspect(Time.utc_now())
# 13022553808

IO.inspect(Time.utc_now())
data.()
|> Day22.solve2()
|> IO.inspect(label: "\n*** Part 2 solution (example: )")
IO.inspect(Time.utc_now())
# 1555