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

Day 11: Corporate Policy

2015/11.livemd

Day 11: Corporate Policy

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

Input

input = Kino.Input.textarea("Please paste your puzzle input:")

Shared Modules

defmodule PasswordGenerator do
  @doc """
  Finds the next valid password, given a string.

  ## Example
  ```elixir
  iex> PasswordGenerator.next_password("abcdefgh")
  "abcdffaa"
  ```
  """
  def next_password(str) do
    new = next(str)
    if valid?(new), do: new, else: next_password(new)
  end

  # checks if a password is valid
  defp valid?(str) do
    not has_illegal_letter?(str) &&
      has_two_pairs?(str) &&
      has_straight?(str)
  end

  # increments the string
  # ie. "avezxzz" maps to "avezyaa"
  defp next(str) do
    str
    |> String.to_charlist()
    |> Enum.reverse()
    |> Enum.map_reduce(:inc, fn
      char, :halt -> {char, :halt}
      ?z, :inc -> {?a, :inc}
      char, :inc -> {char + 1, :halt}
    end)
    |> elem(0)
    |> Enum.reverse()
    |> List.to_string()
  end

  # does string have a straight of three ie. "abc" 
  defp has_straight?(str) do
    str
    |> String.to_charlist()
    |> Enum.chunk_every(3, 1, :discard)
    |> Enum.any?(fn [a, b, c] -> c == b + 1 && b == a + 1 end)
  end

  # does string have any illegal letters "i", "o" or "l"
  defp has_illegal_letter?(str) do
    str
    |> String.contains?(["i", "o", "l"])
  end

  # does string contain two non-overlapping pairs of letters
  defp has_two_pairs?(str) do
    str
    |> count_pairs()
    |> then(&(&1 >= 2))
  end

  # counts all non-overlapping pairs of letters
  defp count_pairs(str) do
    str
    |> String.to_charlist()
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.reduce({:cont, 0}, fn
      _elts, {:skip, count} -> {:cont, count}
      [a, b], {:cont, count} when a == b -> {:skip, count + 1}
      _elts, {:cont, count} -> {:cont, count}
    end)
    |> elem(1)
  end
end

Part 1

input
|> Kino.Input.read()
|> PasswordGenerator.next_password()

Part 2

input
|> Kino.Input.read()
|> PasswordGenerator.next_password()
|> PasswordGenerator.next_password()