Pattern matching nested tuples

This is listed as one of the “Hard” problem in 7 languages 7 weeks book. "Represent a tree of sentences as tuples. Traverse the tree, presenting an indented list.

I think I have solved it, but I’m wondering if there is a cleaner way to do this without all the conditionals. Thanks!

Book example:

{“See spot run.”, {“See spot sit”, “See spot run”}} will print

See spot run.
See spot sit
See spot run

And my example:

TreeRecurse.print_tree({"See spot.", {{"See spot sit.", {"Sitting down.", {"Run spot", {"Runnn!"}}}}, "See spot run."}}, "")

will produce the output:

See spot.
See spot sit.
Sitting down.
Run spot
Runnn!
See spot run.

defmodule TreeRecurse do
  def print_tree({leaf}, indent), do: IO.puts "#{indent}#{leaf}"

  def print_tree({root,{lone}},indent) do
    IO.puts "#{indent}#{root}"

    if is_tuple(lone) do
      print_tree(lone, indent <> " ")
    else
      print_tree({lone}, indent <> " ")
    end

  end

  def print_tree({root,{left,right}},indent) do

    IO.puts "#{indent}#{root}"

    if is_tuple(left) do
      print_tree(left, indent <> " ")
    else
      print_tree({left}, indent <> " ")
    end

    if is_tuple(right) do
      print_tree(right, indent <> " ")
    else
      print_tree({right}, indent <> " ")
    end

  end

end

This is a nice fit for a case with some guards:

  def print_tree2(t, indent) do       
    case t do
      {a, b} when is_binary(a) -> 
        IO.puts "#{indent}#{a}"   
        print_tree2(b, indent <> " ") 
      {a, b} when is_binary(b) ->
        print_tree2(a, indent <> " ") 
        IO.puts "#{indent}#{b}"
      {a} when is_binary(a) ->
        IO.puts "#{indent}#{a}"
    end 
  end
1 Like

I always prefer function clauses instead of conditionals (if/2, case/2, cond/1) whenever possible.

defmodule TreeRecurse do
  @indent 2
  
  def print_tree(tree, indent \\ 0, acc \\ []) do
    tree
    |> traverse_tree(indent, acc)
    |> Enum.join("\n")
    |> IO.puts()
  end
  
  defp traverse_tree(leaf, indent, acc) when is_binary(leaf),
    do: [indented(leaf, indent) | acc]
 
  defp traverse_tree({leaf}, indent, acc),
    do: traverse_tree(leaf, indent, acc)

  defp traverse_tree({head, tail}, indent, acc) when is_binary(head) and is_binary(tail),
    do: [indented(head, indent), indented(tail, indent) | acc]
  
  defp traverse_tree({head, tail}, indent, acc) when is_binary(head),
    do: [indented(head, indent) | traverse_tree(tail, indent + 1, acc)] ++ acc
  
  defp traverse_tree({head, tail}, indent, acc) when is_binary(tail),
    do: traverse_tree(head, indent + 1, acc) ++ [indented(tail, indent) | acc]
  
  defp traverse_tree({head, tail}, indent, acc),
    do: traverse_tree(head, indent + 1, acc) ++ traverse_tree(tail, indent + 1, acc) ++ acc
  
  defp indented(input, indent) when is_number(indent),
    do: ' ' |> List.duplicate(indent * @indent) |> to_string() |> Kernel.<>(input) 
end
2 Likes

Nice, I will have to digest this a bit. It is very clean, but also somewhat verbose from my perspective. More of an engineering solution. Thanks!

Love it, thanks! I figured out that in the end I would need to pattern match a string, so this is exactly what I was looking for. I ended up pattern match “” <> string, but from what I read about unicode strings is_binary or a binary pattern match may be more robust. See my updated solution:

  def print_tree("" <> string, indent), do: IO.puts "#{indent}#{string}"
  def print_tree({leaf}, indent), do: IO.puts "#{indent}#{leaf}"
  def print_tree({root,{lone}},indent) do
    IO.puts "#{indent}#{root}"
    print_tree(lone, indent <> " ")
  end
  def print_tree({root,{left,right}},indent) do
    IO.puts "#{indent}#{root}"
    print_tree(left, indent <> " ")
    print_tree(right, indent <> " ")
  end