ZipperEx behaviour (ZipperEx v0.2.0) View Source
An Elixir implementation for Zipper based on the paper Functional pearl: the zipper. by Gérard Huet.
ZipperEx provides functions to handle an traverse tree data structures.
Usage
To create a zipper you can use the ZipperEx.Zipable protocol or create a
module.
Creating a zipper module
Imagine we have a tree structure based on a tuple with an integer value and a list of cildren. The leafs of the tree are also integers. A tree may then look like the following:
{1, [2, {3, [4, 5]}, 6, {7, [0]}]}
# 1
# +-+-+---+-+
# 2 3 6 7
# +-+-+ +
# 4 5 0To handle this tree we create the following module:
defmodule Support.Zipper do
use ZipperEx
@impl ZipperEx
def branch?(item) do
case item do
{_, [_ | _]} -> true
_else -> false
end
end
@impl ZipperEx
def children({_, children}), do: children
@impl ZipperEx
def make_node({value, _children}, children), do: {value, children}
def make_node(value, children), do: {value, children}
endFor use ZipperEx we have to implement the callbacks branch?/1,
children/1 and make_node/2.
The Support.Zipper module contains all functions the ZipperEx also
provides.
We can now use Support.Zipper.
iex> alias Support.Zipper
iex> zipper = Zipper.new({1, [2, {3, [4, 5]}, 6, {7, [0]}]})
#ZipperEx<{1, [2, {3, [4, 5]}, 6, {7, [0]}]}>
iex> zipper = Zipper.down(zipper)
#ZipperEx<2>
iex> zipper = Zipper.right(zipper)
#ZipperEx<{3, [4, 5]}>
iex> Zipper.remove(zipper) |> Zipper.root()
{1, [2, 6, {7, [0]}]}
iex> {_zipper, acc} = zipper |> ZipperEx.top() |> ZipperEx.traverse([], fn
...> %{loc: {_value, _children}} = zipper, acc -> {zipper, acc}
...> %{loc: value} = zipper, acc -> {zipper, [value | acc]}
...> end)
iex> acc
[0, 6, 5, 4, 2]
Using the ZipperEx.Zipable protocol
Similar to the module example the protocol needs implementations for
ZipperEx.Zipable.branch?/1, ZipperEx.Zipable.children/1 and
ZipperEx.Zipable.make_node/2.
defmodule Support.TreeNode do
@moduledoc false
defstruct value: nil, children: []
def new({value, children}) do
struct!(__MODULE__, value: value, children: Enum.map(children, &new/1))
end
def new(value), do: struct!(__MODULE__, value: value)
defimpl ZipperEx.Zipable do
def branch?(%{children: [_ | _]}), do: true
def branch?(_node), do: false
def children(%{children: children}), do: children
def make_node(node, children), do: %{node | children: children}
end
defimpl Inspect do
def inspect(node, _opts), do: "#TreeNode<#{node.value}, #{inspect(node.children)}>"
end
endSupport.TreeNode can then be used as follows:
iex> alias Support.TreeNode
iex> tree = TreeNode.new({1, [2, {3, [4, 5]}]})
iex> zipper = ZipperEx.new(tree)
iex> ZipperEx.down(zipper)
#ZipperEx<#TreeNode<2, []>>
iex> zipper |> ZipperEx.down() |> ZipperEx.rightmost()
#ZipperEx<#TreeNode<3, [#TreeNode<4, []>, #TreeNode<5, []>]>>
iex> {_zipper, acc} = ZipperEx.traverse(zipper, [], fn z, acc ->
...> node = ZipperEx.node(z)
...> {z, [node.value | acc]}
...> end)
iex> acc
[5, 4, 3, 2, 1]Example supporters
The module Support.Zipper and Support.TreeNode are also used in the
examples for this module.
Link to this section Summary
Callbacks
Should return true if the given node is a branch.
Returns the children of the given node.
Creates a node from the given node and children.
Functions
Appends a child to the given zipper.
Returns true if the given node is a branch.
Returns the children of the given node.
Returns the leftmost child node of the given zipper.
Returns true when the zipper has reached the end.
Returns the first zipper for which fun returns a truthy value. If no such
zipper is found, returns nil.
Inserts a child to the given zipper at leftmost position.
Inserts a child as a left sibling to the given zipper.
Inserts a child as a right sibling to the given zipper.
Returns the left sibling of the given zipper.
Returns the leftmost sibling of the given zipper.
Creates a node from the given node and children.
Returns a zipper where each node is the result of invoking fun on each
corresponding node of the zipper.
Returns a new %ZipperEx{} from a given tree or %ZipperEx.
Returns the next zipper for the given zipper.
Returns the node from the given zipper.
Returns the previours zipper for the given zipper.
Removes the given zipper.
Replaces the zipper with a zipper with the given node.
Returns the right sibling of the given zipper.
Returns the rightmost sibling of the given zipper.
Returns the root node.
Returns the top level zipper.
Traverses the tree for the given zipper in depth-first pre-order and invokes
fun for each zipper along the way.
Traverses the tree for the given zipper in depth-first pre-order and invokes
fun for each zipper along the way with the accumulator.
Traverses the tree for the given zipper in depth-first pre-order until
fun returns {:halt, zipper}. A subtree will be skipped if fun returns
{:skip, zipper}.
Traverses the tree for the given zipper in depth-first pre-order until
fun returns {:halt, zipper, acc}. A subtree will be skipped if fun
returns {:skip, zipper, acc}.
Returns the parent zipper of the given zipper.
Updates the node of the given zipper.
Link to this section Types
Specs
Specs
t(tree) :: %ZipperEx{
left: [tree],
loc: tree,
module: module() | nil,
path: t(tree) | nil | :end,
right: [tree]
}
The ZipperEx type:
loc: The current location of the zipper.left: The left side.left: The right side.path: The path to the top.module: The zipper module.
Specs
tree() :: term()
Link to this section Callbacks
Specs
Should return true if the given node is a branch.
Specs
Returns the children of the given node.
Specs
Creates a node from the given node and children.
Link to this section Functions
Specs
Appends a child to the given zipper.
Examples
iex> alias Support.Zipper
iex> zipper = Zipper.new(1)
#ZipperEx<1>
iex> zipper = Zipper.append_child(zipper, 2)
#ZipperEx<{1, [2]}>
iex> Zipper.append_child(zipper, 3)
#ZipperEx<{1, [2, 3]}>
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2]}))
iex> ZipperEx.append_child(zipper, TreeNode.new(3))
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
Specs
Returns true if the given node is a branch.
Specs
Returns the children of the given node.
Specs
Returns the leftmost child node of the given zipper.
Returns nil if the zipper is not a branch.
Examples
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
iex> zipper = ZipperEx.down(zipper)
#ZipperEx<#TreeNode<2, []>>
iex> ZipperEx.down(zipper)
nil
Specs
Returns true when the zipper has reached the end.
A zipper reached his end by using traverse/2, traverse/3 or calling
next/1 until the end.
Examples
iex> zipper = Support.Zipper.new({1, [2]})
iex> ZipperEx.end?(zipper)
false
iex> zipper |> ZipperEx.traverse(&Function.identity/1) |> ZipperEx.end?()
true
iex> zipper |> ZipperEx.next() |> ZipperEx.next() |> ZipperEx.end?()
true
Specs
Returns the first zipper for which fun returns a truthy value. If no such
zipper is found, returns nil.
Runs through the tree in a depth-first pre-order.
Examples
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
iex> ZipperEx.find(zipper, fn node -> ZipperEx.branch?(node) == false end)
#ZipperEx<#TreeNode<2, []>>
Specs
Inserts a child to the given zipper at leftmost position.
Examples
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [3]}))
iex> ZipperEx.insert_child(zipper, TreeNode.new(2))
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
Specs
Inserts a child as a left sibling to the given zipper.
Raises an ArgumentError when called with an top level zipper.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [3]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper |> ZipperEx.insert_left(TreeNode.new(2)) |> ZipperEx.top()
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
Specs
Inserts a child as a right sibling to the given zipper.
Raises an ArgumentError when called with an top level zipper.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [2]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper |> ZipperEx.insert_right(TreeNode.new(3)) |> ZipperEx.top()
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
Specs
Returns the left sibling of the given zipper.
Returns nil if the zipper doesn't have a left sibling.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [10, 11]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper = ZipperEx.right(zipper)
#ZipperEx<#TreeNode<11, []>>
iex> ZipperEx.right(zipper)
nil
iex> zipper = ZipperEx.left(zipper)
#ZipperEx<#TreeNode<10, []>>
iex> ZipperEx.left(zipper)
nil
Specs
Returns the leftmost sibling of the given zipper.
Returns the zipper himself if the given zipper is the leftmost sibling.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [10, 11, 12]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper = ZipperEx.rightmost(zipper)
#ZipperEx<#TreeNode<12, []>>
iex> ZipperEx.leftmost(zipper)
#ZipperEx<#TreeNode<10, []>>
Specs
Creates a node from the given node and children.
Specs
Returns a zipper where each node is the result of invoking fun on each
corresponding node of the zipper.
Runs through the tree in depth-first per-order.
Examples
iex> alias Support.Zipper
iex> zipper = Zipper.new({1, [2, {3, [400, 500]}, 6]})
iex> Zipper.map(zipper, fn
...> {value, children} -> {value * 2, children}
...> value -> value * 2
...> end)
#ZipperEx<{2, [4, {6, [800, 1000]}, 12]}>
Specs
Returns a new %ZipperEx{} from a given tree or %ZipperEx.
Examples
iex> alias Support.Zipper
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper |> ZipperEx.next() |> ZipperEx.new()
#ZipperEx<{2, [3, 4]}>
iex> zipper |> Zipper.next() |> ZipperEx.new()
#ZipperEx<{2, [3, 4]}>
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2]}))
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>]>>
iex> zipper |> ZipperEx.next() |> ZipperEx.new()
#ZipperEx<#TreeNode<2, []>>
Specs
Returns the next zipper for the given zipper.
The function walks through the tree in a depth-first pre-order. After the last
Returns to the root after the last zipper and marks the zipper as ended. An
ended zipper is detectable via end?/1. Calling next/1 for
an ended zipper returns the given zipper.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<{2, [3, 4]}>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<3>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<4>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<5>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> ZipperEx.next(zipper)
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> ZipperEx.end?(zipper)
true
Specs
Returns the node from the given zipper.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> ZipperEx.node(zipper)
{1, [{2, [3, 4]}, 5]}
Specs
Returns the previours zipper for the given zipper.
The function walks through the tree in the opposit direction as next/1.
If no previous zipper is availble, nil will be returned.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<{2, [3, 4]}>
iex> zipper = ZipperEx.next(zipper)
#ZipperEx<3>
iex> zipper = ZipperEx.prev(zipper)
#ZipperEx<{2, [3, 4]}>
iex> zipper = ZipperEx.prev(zipper)
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> ZipperEx.prev(zipper)
nil
Specs
Removes the given zipper.
The function returns the previous zipper. If remove/1 is called with an
top level zipper an ArgumentError will be raised.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = ZipperEx.down(zipper)
#ZipperEx<{2, [3, 4]}>
iex> ZipperEx.remove(zipper)
#ZipperEx<{1, [5]}>
Specs
Replaces the zipper with a zipper with the given node.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper |> ZipperEx.down() |> ZipperEx.replace(7) |> ZipperEx.root()
{1, [7, 5]}
Specs
Returns the right sibling of the given zipper.
Returns nil if the zipper doesn't have a right sibling.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [10, 11]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper = ZipperEx.right(zipper)
#ZipperEx<#TreeNode<11, []>>
iex> ZipperEx.right(zipper)
nil
iex> zipper = ZipperEx.left(zipper)
#ZipperEx<#TreeNode<10, []>>
iex> ZipperEx.left(zipper)
nil
Specs
Returns the rightmost sibling of the given zipper.
Returns the zipper himself if the given zipper is the rightmost sibling.
Examples
iex> alias Support.TreeNode
iex> zipper =
...> TreeNode.new({1, [10, 11, 12]})
...> |> ZipperEx.new()
...> |> ZipperEx.down()
iex> zipper = ZipperEx.rightmost(zipper)
#ZipperEx<#TreeNode<12, []>>
iex> ZipperEx.rightmost(zipper)
#ZipperEx<#TreeNode<12, []>>
Specs
Returns the root node.
Examples
iex> alias Support.Zipper
iex> zipper = Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = zipper |> Zipper.down() |> Zipper.down()
#ZipperEx<3>
iex> Zipper.root(zipper)
{1, [{2, [3, 4]}, 5]}
Specs
Returns the top level zipper.
Examples
iex> alias Support.Zipper
iex> zipper = Zipper.new({1, [{2, [3, 4]}, 5]})
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
iex> zipper = zipper |> Zipper.down() |> Zipper.down()
#ZipperEx<3>
iex> Zipper.top(zipper)
#ZipperEx<{1, [{2, [3, 4]}, 5]}>
Specs
Traverses the tree for the given zipper in depth-first pre-order and invokes
fun for each zipper along the way.
If the zipper is not at the top, just the subtree will be traversed.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
iex> ZipperEx.traverse(zipper, fn z ->
...> ZipperEx.update(z, fn
...> {value, children} -> {value + 100, children}
...> value -> value + 200
...> end)
...> end)
#ZipperEx<{101, [{102, [203, 204]}, 205]}>
iex> {1, [{2, [3, 4]}, 5]}
...> |> Support.Zipper.new()
...> |> ZipperEx.down()
...> |> ZipperEx.traverse(fn z ->
...> ZipperEx.update(z, fn
...> {value, children} -> {value + 100, children}
...> value -> value + 200
...> end)
...> end)
...> |> ZipperEx.root()
{1, [{102, [203, 204]}, 5]}
Specs
Traverses the tree for the given zipper in depth-first pre-order and invokes
fun for each zipper along the way with the accumulator.
If the zipper is not at the top, just the subtree will be traversed.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
iex> {zipper, acc} = ZipperEx.traverse(zipper, [], fn z, acc ->
...> updated = ZipperEx.update(z, fn
...> {value, children} -> {value + 100, children}
...> value -> value + 200
...> end)
...> {updated, [ZipperEx.node(z) | acc]}
...> end)
iex> zipper
#ZipperEx<{101, [{102, [203, 204]}, 205]}>
iex> acc
[5, 4, 3, {2, [3, 4]}, {1, [{2, [3, 4]}, 5]}]
Specs
Traverses the tree for the given zipper in depth-first pre-order until
fun returns {:halt, zipper}. A subtree will be skipped if fun returns
{:skip, zipper}.
If the zipper is not at the top, just the subtree will be traversed.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
iex> ZipperEx.traverse_while(zipper, fn z ->
...> case ZipperEx.node(z) do
...> {2, _children} ->
...> {:halt, z}
...> _else ->
...> {:cont, ZipperEx.update(z, fn
...> {value, children} -> {value + 100, children}
...> value -> value + 200
...> end)}
...> end
...> end)
#ZipperEx<{101, [{2, [3, 4]}, 5]}>
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, 5]})
iex> ZipperEx.traverse_while(zipper, fn z ->
...> case ZipperEx.node(z) do
...> {2, _children} ->
...> {:skip, z}
...> _else ->
...> {:cont, ZipperEx.update(z, fn
...> {value, children} -> {value + 100, children}
...> value -> value + 200
...> end)}
...> end
...> end)
#ZipperEx<{101, [{2, [3, 4]}, 205]}>
Specs
traverse_while( t(), acc, (t(), acc -> {:cont, t(), acc} | {:halt, t(), acc} | {:skip, t(), acc}) ) :: {t(), acc} when acc: term()
Traverses the tree for the given zipper in depth-first pre-order until
fun returns {:halt, zipper, acc}. A subtree will be skipped if fun
returns {:skip, zipper, acc}.
If the zipper is not at the top, just the subtree will be traversed.
Examples
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, {5, [6]}, 7]})
iex> {zipper, acc} = ZipperEx.traverse_while(zipper, [], fn z, acc ->
...> case ZipperEx.node(z) do
...> {5, _children} ->
...> {:halt, z, acc}
...> _else ->
...> updated = ZipperEx.update(z, fn
...> {value, children} -> {value + 100, children}
...> value -> value + 200
...> end)
...> {:cont, updated, [ZipperEx.node(z) | acc]}
...> end
...> end)
iex> zipper
#ZipperEx<{101, [{102, [203, 204]}, {5, [6]}, 7]}>
iex> acc
[4, 3, {2, [3, 4]}, {1, [{2, [3, 4]}, {5, [6]}, 7]}]
iex> zipper = Support.Zipper.new({1, [{2, [3, 4]}, {5, [6]}, 7]})
iex> {zipper, acc} = ZipperEx.traverse_while(zipper, [], fn z, acc ->
...> case ZipperEx.node(z) do
...> {5, _children} ->
...> {:skip, z, acc}
...> _else ->
...> updated = ZipperEx.update(z, fn
...> {value, children} -> {value + 100, children}
...> value -> value + 200
...> end)
...> {:cont, updated, [ZipperEx.node(z) | acc]}
...> end
...> end)
iex> zipper
#ZipperEx<{101, [{102, [203, 204]}, {5, [6]}, 207]}>
iex> acc
[7, 4, 3, {2, [3, 4]}, {1, [{2, [3, 4]}, {5, [6]}, 7]}]
Specs
Returns the parent zipper of the given zipper.
Returns nil if the zipper is the root.
Examples
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
iex> zipper = ZipperEx.down(zipper)
#ZipperEx<#TreeNode<2, []>>
iex> ZipperEx.up(zipper)
#ZipperEx<#TreeNode<1, [#TreeNode<2, []>, #TreeNode<3, []>]>>
Specs
Updates the node of the given zipper.
iex> alias Support.TreeNode
iex> zipper = ZipperEx.new(TreeNode.new({1, [2, 3]}))
iex> zipper = ZipperEx.down(zipper)
iex> zipper = ZipperEx.update(zipper, fn %TreeNode{} = node ->
...> %{node | value: 99}
...> end)
#ZipperEx<#TreeNode<99, []>>
iex> ZipperEx.root(zipper)
#TreeNode<1, [#TreeNode<99, []>, #TreeNode<3, []>]>