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

Day 6

elixir/day6.livemd

Run in Livebook

Day 6

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

Puzzle Input

input = Kino.Input.textarea("Paste puzzle input")

Module

defmodule Day6 do
  def reduce_until_marker(element, acc) do
    count = Enum.count(acc)

    cond do
      # we need at least 4 characters to compare
      3 > count ->
        {:cont, [element | acc]}

      # we have 4 characters (acc + current), are they unique?
      true ->
        new_count = Enum.count([element | acc])
        uniq_count = Enum.uniq_by([element | acc], fn {x, _} -> x end) |> Enum.count()

        cond do
          # all characters are unique, return the position of the newest character
          new_count == uniq_count ->
            {_, marker_index} = element
            {:halt, marker_index + 1}

          # all characters are not unique, remove the oldest character and start over
          true ->
            [_ | rest] = Enum.reverse(acc)
            {:cont, [element | Enum.reverse(rest)]}
        end
    end
  end

  def reduce_until_marker_part_2(element, acc) do
    count = Enum.count(acc)

    cond do
      # we need at least 14 characters to compare
      13 > count ->
        {:cont, [element | acc]}

      # we have 14 characters (acc + current), are they unique?
      true ->
        new_count = Enum.count([element | acc])
        uniq_count = Enum.uniq_by([element | acc], fn {x, _} -> x end) |> Enum.count()

        cond do
          # all characters are unique, return the position of the newest character
          new_count == uniq_count ->
            {_, marker_index} = element
            {:halt, marker_index + 1}

          # all characters are not unique, remove the oldest character and start over
          true ->
            [_ | rest] = Enum.reverse(acc)
            {:cont, [element | Enum.reverse(rest)]}
        end
    end
  end

  def find_first_marker(input) do
    input
    |> String.split("", trim: true)
    |> Enum.with_index()
    |> Enum.reduce_while([], &Day6.reduce_until_marker/2)
  end

  def find_first_message_marker(input) do
    input
    |> String.split("", trim: true)
    |> Enum.with_index()
    |> Enum.reduce_while([], &Day6.reduce_until_marker_part_2/2)
  end
end

Part 1

ExUnit.start(auto_run: false)

defmodule ExampleTest do
  use ExUnit.Case, async: false

  describe "find first marker after:" do
    test "character 5" do
      assert Day6.find_first_marker("bvwbjplbgvbhsrlpgdmjqwftvncz") == 5
    end

    test "character 6" do
      assert Day6.find_first_marker("nppdvjthqldpwncqszvftbrmjlhg") == 6
    end

    test "character 7" do
      assert Day6.find_first_marker("mjqjpqmgbljsphdztnvjfqwrcgsmlb") == 7
    end

    test "character 10" do
      assert Day6.find_first_marker("nznrnfrfntjfmvfwmzdfjlvtqnbhcprsg") == 10
    end

    test "character 11" do
      assert Day6.find_first_marker("zcfzfwzzqfrljwzlrfnpqdbhtmscgvjw") == 11
    end
  end
end

ExUnit.run()
# Part 1

Kino.Input.read(input)
|> Day6.find_first_marker()

Part 2

ExUnit.start(auto_run: false)

defmodule ExampleTest do
  use ExUnit.Case, async: false

  describe "find first message marker after:" do
    test "character 19" do
      assert Day6.find_first_message_marker("mjqjpqmgbljsphdztnvjfqwrcgsmlb") == 19
    end

    test "character 23" do
      assert Day6.find_first_message_marker("bvwbjplbgvbhsrlpgdmjqwftvncz") == 23
    end

    test "character 23 again" do
      assert Day6.find_first_message_marker("nppdvjthqldpwncqszvftbrmjlhg") == 23
    end

    test "character 29" do
      assert Day6.find_first_message_marker("nznrnfrfntjfmvfwmzdfjlvtqnbhcprsg") == 29
    end

    test "character 26" do
      assert Day6.find_first_message_marker("zcfzfwzzqfrljwzlrfnpqdbhtmscgvjw") == 26
    end
  end
end

ExUnit.run()
# Part 2

Kino.Input.read(input)
|> Day6.find_first_message_marker()