defmodule BalancedOctopus do @opening_to_closing %{"(" => ")", "[" => "]", "{" => "}"} @closing_to_opening %{")" => "(", "]" => "[", "}" => "{"} def balanced?(text, stack \\ []) def balanced?("", []), do: true def balanced?(_text, :error), do: false def balanced?("", _pending_stack), do: false def balanced?(text, stack) do {current_char, next_text} = String.split_at(text, 1) updated_stack = maybe_stack(stack, current_char) balanced?(next_text, updated_stack) end defp maybe_stack(stack, char) do cond do Map.has_key?(@opening_to_closing, char) -> stack_push(stack, char) Map.has_key?(@closing_to_opening, char) -> {last_opening, rest} = stack_pop(stack) required_last_opening = @closing_to_opening[char] if last_opening == required_last_opening, do: rest, else: :error true -> stack end end defp stack_push(stack, inserting), do: [inserting | stack] defp stack_pop([]), do: {nil, []} defp stack_pop([popped | rest] = _stack), do: {popped, rest} end [ ["abcd", true], ["(abcd", false], ["(abcd)", true], ["(ab[cd)ef]", false], ["(abc[%def])", true], ["$(abc[de]fg{hi}jk)%//", true], ["(({abc})", false] ] |> Enum.each(fn [text, expectation] -> IO.write("Testing #{text} as #{expectation}... ") if BalancedOctopus.balanced?(text) == expectation do IO.puts("✅") else IO.puts("🔴") end end)