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

Day 11: Corporate Policy

elixir/day_11_corporate_policy.livemd

Day 11: Corporate Policy

Section

defmodule P1 do
  def increment_until_valid(password) do
    new_password = P1.increment_string(password)

    if P1.validate_password(new_password) do
      new_password
    else
      increment_until_valid(new_password)
    end
  end

  def increment_string(str) do
    str
    # Convert string to a list of characters
    |> String.to_charlist()
    # Reverse for easier right-to-left processing
    |> Enum.reverse()
    # Process increment logic
    |> increment_reversed()
    # Reverse back to original direction
    |> Enum.reverse()
    # Convert back to a string
    |> List.to_string()
  end

  defp increment_reversed(charlist) do
    do_increment(charlist, true)
  end

  # If all characters have overflowed, prepend 'a'
  defp do_increment([], true), do: ~c"a"

  defp do_increment([h | t], true) do
    case h do
      # If 'z', wrap around to 'a' and carry over
      ?z -> [?a | do_increment(t, true)]
      # Otherwise, increment normally
      _ -> [next_char(h + 1) | t]
    end
  end

  # No carry, retain character
  defp do_increment([h | t], false), do: [h | t]
  defp next_char(char) when char in [?i, ?o, ?l], do: next_char(char + 1)
  defp next_char(char), do: char

  # Public function to validate a password based on the rules.
  def validate_password(password) do
    has_straight(password) and not has_invalid_letters?(password) and has_two_pairs(password)
  end

  # Checks for at least one increasing straight of three letters.
  def has_straight(password) do
    password
    |> String.to_charlist()
    |> has_straight_helper(false)
  end

  defp has_straight_helper([a, b, c | _rest], _) when b == a + 1 and c == b + 1,
    do: true

  defp has_straight_helper([_ | rest], acc),
    do: has_straight_helper(rest, acc)

  defp has_straight_helper([], _),
    do: false

  # Checks if the password contains any of the forbidden letters.
  def has_invalid_letters?(password) do
    String.contains?(password, ["i", "o", "l"])
  end

  # Checks for at least two non-overlapping pairs of the same letter.
  def has_two_pairs(password) do
    pairs =
      password
      |> String.graphemes()
      |> Enum.chunk_every(2, 1, :discard)
      |> Enum.filter(fn [a, b] -> a == b end)
      |> Enum.uniq()

    length(pairs) >= 2
  end
end

P1.increment_until_valid("hepxcrrq")
"hepxxyzz"
P1.increment_until_valid("hepxxyzz")
"heqaabcc"