defmodule Complexity do defmacro with_complexity(fun) do quote do arguments = binding() |> Keyword.values() |> Enum.map(&inspect/1) |> Enum.join(", ") {function, _arity} = __ENV__.function [{:current, current} | _] = Complexity.get() indentation = String.duplicate(" ", current) start() IO.puts("#{indentation}#{__MODULE__}.#{function}(#{arguments}) {") return = unquote(fun).() IO.puts("#{indentation}}=> #{return}") finish() return end end def start() do [current: current, steps: steps, space: space] = get() new_current = current + 1 new_complexity = [ current: new_current, steps: steps + 1, space: if(new_current > space, do: new_current, else: space) ] Process.put(:complexity, new_complexity) end def finish() do case get() do [{:current, 1}, {:steps, steps}, {:space, space}] -> IO.puts(String.duplicate("-", 80)) IO.puts("Total steps (∝ time): #{steps}") IO.puts("Maximum depth (∝ space): #{space}") IO.puts(String.duplicate("-", 80)) Process.delete(:complexity) [{:current, current} | _] = complexity -> new_complexity = Keyword.put(complexity, :current, current - 1) Process.put(:complexity, new_complexity) end end def get() do Process.get(:complexity, current: 0, steps: 0, space: 0) end end defmodule Counter do import Complexity def count_change(amount) do do_count_change(amount, 5) end defp do_count_change(0, _kinds_of_coins) do with_complexity(fn -> 1 end) end defp do_count_change(amount, _kinds_of_coins) when amount < 0 do with_complexity(fn -> 0 end) end defp do_count_change(_amount, 0) do with_complexity(fn -> 0 end) end defp do_count_change(amount, kinds_of_coins) do with_complexity(fn -> do_count_change(amount, kinds_of_coins - 1) + do_count_change(amount - first_denomination(kinds_of_coins), kinds_of_coins) end) end defp first_denomination(1), do: 1 defp first_denomination(2), do: 5 defp first_denomination(3), do: 10 defp first_denomination(4), do: 25 defp first_denomination(5), do: 50 end