rose_tree v0.1.0 RoseTree.Zipper

A zipper provides a mechanism for traversing a tree by focusing on a given node and maintaining enough data to reconstruct the overall tree from any given node.

Because most of the functions in RoseTree.Zipper can attempt to access data that may not exist, e.g. an out-of-bounds index in a node’s array of chlidren, the majority of the functions return values with tagged tuples to let the user explicitly handle success and error cases: {:ok, {%RoseTree{}, []}} | {:error, {:rose_tree, error_message}}

To make working with these values easier, the function lift/2 takes one of these tagged tuple (either zipper | error) values and a function. If the first argument is an error, the error is passed through; if it is an {:ok, zipper} tuple, the function is applied to the zipper. This lets you easily chain successive calls to tree manipulation functions.

Link to this section Summary

Functions

Move up the tree to the parent of the node that the zipper is currently focused on

Move the zipper’s focus to the child at the index

Move the zipper’s focus to the node’s first child that matches a predicate

Move the zipper’s focus to the node’s first child

Build a zipper focusing on the current tree

Move the zipper’s focus to the node’s last child

Lift a function expecting a zipper as an argument to one that can handle either {:ok, zipper} | {:error, error}

Apply a function to the value of a tree under the focus of a zipper

Move the zipper’s focus to the next sibling of the currently focused node

Move the zipper’s focus to the previous sibling of the currently focused node

Remove a subtree from a tree and move focus to the parent node

Ascend from any node to the root of the tree

Extract the currently focused tree from a zipper

Link to this section Types

Link to this type breadcrumb()
breadcrumb() :: %{node: any, index: integer, other_children: [any]}
Link to this type either_zipper()
either_zipper() :: {:ok, RoseTree.Zipper.t} | {:error, tuple}

Link to this section Functions

Link to this function ascend(zipper)
ascend(RoseTree.Zipper.t) ::
  {:ok, RoseTree.Zipper.t} |
  {:error, {:rose_tree, :no_parent}}

Move up the tree to the parent of the node that the zipper is currently focused on.

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(:b),
...>      {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [b, c])
...> end
...> {:ok, descended} = Zipper.descend({tree, []}, 0)
...> Zipper.ascend(descended)
{:ok,
  {%RoseTree{node: :a, children: [
    %RoseTree{node: :b, children: []},
    %RoseTree{node: :c, children: [
      %RoseTree{node: :d, children: []},
      %RoseTree{node: :z, children: []}
    ]}
  ]}, []}
}
Link to this function descend(zipper, index)
descend(RoseTree.Zipper.t, integer) ::
  {:ok, RoseTree.Zipper.t} |
  {:error, {:rose_tree, :no_children}}

Move the zipper’s focus to the child at the index.

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(:b),
...>      {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [b, c])
...> end
...> Zipper.descend({tree, []}, 0)
{:ok, {%RoseTree{node: :b, children: []}, [%{
  node: :a,
  index: 0,
  other_children: [
    %RoseTree{node: :c, children: [
      %RoseTree{node: :d, children: []},
      %RoseTree{node: :z, children: []}
    ]}
  ]
}]}}
Link to this function find_child(zipper, predicate)
find_child(RoseTree.Zipper.t, (any -> any)) ::
  {:ok, RoseTree.Zipper.t} |
  {:error, {:rose_tree, :no_child_match}}

Move the zipper’s focus to the node’s first child that matches a predicate.

Examples

iex> {:ok, tree} = with {:ok, d} <- RoseTree.new(15),
...>      {:ok, z} <- RoseTree.new(5),
...>      {:ok, c} <- RoseTree.new(1, [d, z]) do
...>   RoseTree.new(:a, [c])
...> end
...> d_focus = tree
...> |> Zipper.from_tree()
...> |> Zipper.descend(0)
...> |> Zipper.lift(&Zipper.find_child(&1, fn(child) -> child.node > 10 end))
{:ok, {
  %RoseTree{children: [], node: 15}, [
    %{index: 0, node: 1, other_children: [%RoseTree{children: [], node: 5}]},
    %{index: 0, node: :a, other_children: []}
  ]
}}

iex> {:ok, tree} = with {:ok, d} <- RoseTree.new(15),
...>      {:ok, z} <- RoseTree.new(5),
...>      {:ok, c} <- RoseTree.new(1, [d, z]) do
...>   RoseTree.new(:a, [c])
...> end
...> d_focus = tree
...> |> Zipper.from_tree()
...> |> Zipper.descend(0)
...> |> Zipper.lift(&Zipper.find_child(&1, fn(child) -> length(child.children) > 5 end))
{:error, {:rose_tree, :no_child_match}}
Link to this function first_child(zipper)
first_child(RoseTree.Zipper.t) ::
  {:ok, RoseTree.Zipper.t} |
  {:error, {:rose_tree, :no_children}}

Move the zipper’s focus to the node’s first child.

If the node is a leaf (and thus has no children), returns {:error, {:rose_tree, :no_children}}. Otherwise, returns {:ok, zipper}

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(:b), …> {:ok, d} <- RoseTree.new(:d), …> {:ok, z} <- RoseTree.new(:z), …> {:ok, c} <- RoseTree.new(:c, [d, z]) do …> RoseTree.new(:a, [b, c]) …> end …> tree …> |> Zipper.from_tree() …> |> Zipper.first_child() {:ok, {%RoseTree{node: :b, children: []}, [%{ node: :a, index: 0, other_children: [

%RoseTree{node: :c, children: [
  %RoseTree{node: :d, children: []},
  %RoseTree{node: :z, children: []}
]}

] }]}}

Build a zipper focusing on the current tree.

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(:b),
...>      {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [b, c])
...> end
...> Zipper.from_tree(tree)
{%RoseTree{node: :a, children: [
  %RoseTree{node: :b, children: []},
  %RoseTree{node: :c, children: [
    %RoseTree{node: :d, children: []},
    %RoseTree{node: :z, children: []}
  ]}
]}, []}
Link to this function last_child(zipper)
last_child(RoseTree.Zipper.t) ::
  {:ok, RoseTree.Zipper.t} |
  {:error, {:rose_tree, :no_children}}

Move the zipper’s focus to the node’s last child.

If the node is a leaf (and thus has no children), returns {:error, {:rose_tree, :no_children}}. Otherwise, returns {:ok, zipper}

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(:b), …> {:ok, d} <- RoseTree.new(:d), …> {:ok, z} <- RoseTree.new(:z), …> {:ok, c} <- RoseTree.new(:c, [d, z]) do …> RoseTree.new(:a, [b, c]) …> end …> tree …> |> Zipper.from_tree() …> |> Zipper.last_child() {:ok, { %RoseTree{node: :c, children: [

%RoseTree{node: :d, children: []},
%RoseTree{node: :z, children: []}

]}, [%{node: :a, index: 1, other_children: [%RoseTree{node: :b, children: []}]}] }}

Link to this function lift(either_zipper_error, f)
lift(either_zipper, (any -> any)) :: either_zipper

Lift a function expecting a zipper as an argument to one that can handle either {:ok, zipper} | {:error, error}.

If the first argument is an :error tuple, the error is passed through. Otherwise, the function is applied to the zipper in the :ok tuple.

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(:b),
...>      {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [b, c])
...> end
...> tree
...> |> Zipper.from_tree()
...> |> Zipper.descend(1)
...> |> Zipper.lift(&Zipper.descend(&1, 0))
...> |> Zipper.lift(&Zipper.to_tree(&1))
%RoseTree{node: :d, children: []}

...> tree
...> |> Zipper.from_tree()
...> |> Zipper.descend(1)
...> |> Zipper.lift(&Zipper.descend(&1, 0))
...> |> Zipper.lift(&Zipper.descend(&1, 1))
{:error, {:rose_tree, :bad_path}}
Link to this function modify(zipper, f)
modify(RoseTree.Zipper.t, (any -> any)) :: RoseTree.Zipper.t

Apply a function to the value of a tree under the focus of a zipper.

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(1), …> {:ok, d} <- RoseTree.new(11), …> {:ok, z} <- RoseTree.new(12), …> {:ok, c} <- RoseTree.new(10, [d, z]) do …> RoseTree.new(0, [b, c]) …> end …> {:ok, descended} = Zipper.descend({tree, []}, 0) …> Zipper.modify(descended, fn(x) -> x * 5 end) {%RoseTree{node: 5, children: []}, [%{

node: 0,
index: 0,
other_children: [
  %RoseTree{node: 10, children: [
    %RoseTree{node: 11, children: []},
    %RoseTree{node: 12, children: []}
  ]}
]

}]}

Link to this function next_sibling(zipper)
next_sibling(RoseTree.Zipper.t) ::
  {:ok, RoseTree.Zipper.t} |
  {:error, {:rose_tree, :no_siblings}} |
  {:error, {:rose_tree, :no_next_sibling}}

Move the zipper’s focus to the next sibling of the currently focused node.

Examples

iex> {:ok, tree} = with {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [c])
...> end
...> tree
...> |> Zipper.from_tree()
...> |> Zipper.descend(0)
...> |> Zipper.lift(&Zipper.descend(&1, 0))
...> |> Zipper.lift(&Zipper.next_sibling/1)
{:ok, {
  %RoseTree{children: [], node: :z}, [
    %{index: 1, node: :c, other_children: [%RoseTree{children: [], node: :d}]},
    %{index: 0, node: :a, other_children: []}
  ]
}}

iex> {:ok, tree} = with {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [c])
...> end
...> tree
...> |> Zipper.from_tree()
...> |> Zipper.descend(0)
...> |> Zipper.lift(&Zipper.descend(&1, 1))
...> |> Zipper.lift(&Zipper.next_sibling/1)
{:error, {:rose_tree, :no_next_sibling}}
Link to this function previous_sibling(zipper)
previous_sibling(RoseTree.Zipper.t) ::
  {:ok, RoseTree.Zipper.t} |
  {:error, {:rose_tree, :no_siblings}} |
  {:error, {:rose_tree, :no_previous_sibling}}

Move the zipper’s focus to the previous sibling of the currently focused node.

Examples

iex> {:ok, tree} = with {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [c])
...> end
...> d_focus = tree
...> |> Zipper.from_tree()
...> |> Zipper.descend(0)
...> |> Zipper.lift(&Zipper.descend(&1, 1))
...> |> Zipper.lift(&Zipper.previous_sibling/1)
{:ok, {
  %RoseTree{children: [], node: :d}, [
    %{index: 0, node: :c, other_children: [%RoseTree{children: [], node: :z}]},
    %{index: 0, node: :a, other_children: []}
  ]
}}

iex> {:ok, tree} = with {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [c])
...> end
...> d_focus = tree
...> |> Zipper.from_tree()
...> |> Zipper.descend(0)
...> |> Zipper.lift(&Zipper.descend(&1, 0))
...> |> Zipper.lift(&Zipper.previous_sibling/1)
{:error, {:rose_tree, :no_previous_sibling}}

Remove a subtree from a tree and move focus to the parent node.

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(:b),
...>      {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [b, c])
...> end
...> tree
...> |> Zipper.from_tree()
...> |> Zipper.descend(0)
...> |> Zipper.lift(&Zipper.prune(&1))
...> |> Zipper.to_tree()
%RoseTree{node: :a, children: [
  %RoseTree{node: :c, children: [
    %RoseTree{node: :d, children: []},
    %RoseTree{node: :z, children: []}
  ]}
]}

Ascend from any node to the root of the tree.

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(:b), …> {:ok, d} <- RoseTree.new(:d), …> {:ok, z} <- RoseTree.new(:z), …> {:ok, c} <- RoseTree.new(:c, [d, z]) do …> RoseTree.new(:a, [b, c]) …> end …> tree …> |> Zipper.from_tree() …> |> Zipper.descend(1) …> |> Zipper.lift(&Zipper.descend(&1, 0)) …> |> Zipper.lift(&Zipper.to_root(&1)) %RoseTree{node: :a, children: [

%RoseTree{node: :b, children: []},
%RoseTree{node: :c, children: [
  %RoseTree{node: :d, children: []},
  %RoseTree{node: :z, children: []}
]}

]}

Extract the currently focused tree from a zipper.

Examples

iex> {:ok, tree} = with {:ok, b} <- RoseTree.new(:b),
...>      {:ok, d} <- RoseTree.new(:d),
...>      {:ok, z} <- RoseTree.new(:z),
...>      {:ok, c} <- RoseTree.new(:c, [d, z]) do
...>   RoseTree.new(:a, [b, c])
...> end
...> Zipper.descend({tree, []}, 0)
...> |> Zipper.lift(&Zipper.to_tree(&1))
%RoseTree{node: :b, children: []}