xipper v0.1.0 Xipper

An Elixir implementation of Huet’s Zipper, with gratitude to Rich Hickey’s Clojure implementation.

Zippers provide an elegant solution for traversing a tree-like data structure, while maintaining enough state data to reconstruct the entire tree from any of its child nodes.

All that is required to create a zipper for a data structure is the data structure itself and a set of functions that define behaviours around nodes of the data structure. See Xipper.new/4 for details.

For the sake of brevity, the documentation for this module’s functions will assume the following code has been run before each example:

iex> zipper = Xipper.new(
...>   [1, 2, [3, 4], 5],
...>   &is_list/1,
...>   fn node -> node end,
...>   fn _node, children -> children end
...> )
iex> zipper.focus
[1, 2, [3, 4], 5]

Again, see new/4 for an explanation of the functions passed as arguments here.

Link to this section Summary

Functions

Appends the given child as the right-most child of the current focus, if it is a branch node, without shifting focus

Returns a node’s children if called on a branch node, an error tuple otherwise

Shifts the zipper’s focus down to the leftmost child node of the current focus

Replaces the currently focused node with the result of applying the given function to that node

Returns the current focus of the zipper

Inserts the given child as the left-most child of the current focus, if it is a branch node, without shifting focus

Inserts the given node as the immediate left-hand sibling of the current focus, without shifting focus

Inserts the given node as the immediate right-hand sibling of the current focus, without shifting focus

Returns true if the current focus of the zipper is a branch node, false otherwise

Returns true if the zipper has reached the end of a depth-first walk, false otherwise

Shifts the zipper’s focus to the sibling node directly to the left of the current focus

Moves focus to the leftmost sibling of the current focus

Returns all left-hand siblings of a node

Takes a zipper, a node, and a list of child nodes and returns a new node constructed from the node and children via the user-defined make_node function passed in to Xipper.new/4

Moves to the next node in a depth-first walk through the zipper

Moves to the previous node in a depth-first walk through the zipper

Removes the current focus of the zipper and shifts focus to where the previous node in a depth-first walk would be

Replaces the current focus with the node passed as the second argument

Shifts the zipper’s focus to the sibling node directly to the right of the current focus

Moves focus to the rightmost sibling of the current focus

Returns all right-hand siblings of a node

Traverses a zipper upwards to the root of the zipper

Shifts the zipper’s focus to the current focus’s parent

Link to this section Types

Link to this type children_function()
children_function() :: (any() -> [any()])
Link to this type error()
error() :: {:error, atom()}
Link to this type functions()
functions() :: [
  is_branch: Xipper.is_branch_function(),
  children: Xipper.children_function(),
  make_node: Xipper.make_node_function()
]
Link to this type is_branch_function()
is_branch_function() :: (any() -> boolean())
Link to this type make_node_function()
make_node_function() :: (any(), any(), any() -> any())
Link to this type maybe_zipper()
maybe_zipper() :: Xipper.t() | Xipper.error()
Link to this type parent()
parent() :: [focus: any(), left: any(), right: any()]
Link to this type t()
t() :: %Xipper{
  focus: any(),
  functions: Xipper.functions(),
  is_end: boolean(),
  left: [any()],
  parents: [Xipper.parent()],
  right: [any()]
}

Link to this section Functions

Link to this function append_child(zipper, new_child)
append_child(Xipper.t(), any()) :: Xipper.maybe_zipper()

Appends the given child as the right-most child of the current focus, if it is a branch node, without shifting focus.

This function returns an error if trying to insert a child into a leaf node.

Example

iex> zipper = Xipper.append_child(zipper, [6, 7])
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5, [6, 7]]

iex> zipper |> Xipper.down |> Xipper.append_child(1.5)
{:error, :append_child_of_leaf}
Link to this function children(zipper)
children(Xipper.t()) :: Xipper.maybe_zipper()

Returns a node’s children if called on a branch node, an error tuple otherwise.

Example

iex> Xipper.children(zipper)
[1, 2, [3, 4], 5]

iex> zipper |> Xipper.down |> Xipper.children
{:error, :children_of_leaf}

Shifts the zipper’s focus down to the leftmost child node of the current focus.

This function returns an error tuple if the current focus is a leaf node, or a branch node with no children.

Example

iex> zipper = Xipper.down(zipper)
iex> Xipper.focus(zipper)
1
iex> Xipper.down(zipper)
{:error, :down_from_leaf}
Link to this function edit(zipper, func)
edit(Xipper.t(), (any() -> any())) :: Xipper.t()

Replaces the currently focused node with the result of applying the given function to that node.

Example

iex> zipper = Xipper.down(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.edit(zipper, &to_string/1)
iex> Xipper.focus(zipper)
"1"
Link to this function focus(map)
focus(Xipper.t()) :: any()

Returns the current focus of the zipper.

Example

iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]

iex> zipper |> Xipper.down |> Xipper.focus
1
Link to this function insert_child(zipper, new_child)
insert_child(Xipper.t(), any()) :: Xipper.maybe_zipper()

Inserts the given child as the left-most child of the current focus, if it is a branch node, without shifting focus.

This function returns an error if trying to insert a child into a leaf node.

Example

iex> zipper = Xipper.insert_child(zipper, 0)
iex> Xipper.focus(zipper)
[0, 1, 2, [3, 4], 5]

iex> zipper |> Xipper.down |> Xipper.insert_child(0)
{:error, :insert_child_of_leaf}
Link to this function insert_left(zipper, new_sibling)
insert_left(Xipper.t(), any()) :: Xipper.maybe_zipper()

Inserts the given node as the immediate left-hand sibling of the current focus, without shifting focus.

This function returns an error tuple if called on the root of a zipper.

Example

iex> zipper = Xipper.down(zipper)
iex> zipper = Xipper.insert_left(zipper, -10)
iex> zipper |> Xipper.root |> Xipper.focus
[-10, 1, 2, [3, 4], 5]
Link to this function insert_right(zipper, new_sibling)
insert_right(Xipper.t(), any()) :: Xipper.maybe_zipper()

Inserts the given node as the immediate right-hand sibling of the current focus, without shifting focus.

This function returns an error tuple if called on the root of a zipper.

Example

iex> zipper = Xipper.down(zipper)
iex> zipper = Xipper.insert_right(zipper, 1.5)
iex> zipper |> Xipper.root |> Xipper.focus
[1, 1.5, 2, [3, 4], 5]
Link to this function is_branch(zipper)
is_branch(Xipper.t()) :: boolean()

Returns true if the current focus of the zipper is a branch node, false otherwise.

Example

iex> Xipper.is_branch(zipper)
true

iex> zipper |> Xipper.down |> Xipper.is_branch
false
Link to this function is_end(xipper)
is_end(Xipper.t()) :: boolean()

Returns true if the zipper has reached the end of a depth-first walk, false otherwise.

Example

iex> Xipper.is_end(zipper)
false

iex> zipper |> Xipper.down |> Xipper.rightmost |> Xipper.next |> Xipper.is_end
true

Shifts the zipper’s focus to the sibling node directly to the left of the current focus.

This function returns an error tuple if the current focus is the leftmost of its siblings.

Example

iex> zipper = Xipper.down(zipper)
iex> zipper |> Xipper.left
{:error, :left_of_leftmost}
iex> zipper = zipper |> Xipper.rightmost |> Xipper.left
iex> Xipper.focus(zipper)
[3, 4]
Link to this function leftmost(zipper)
leftmost(Xipper.t()) :: Xipper.t()

Moves focus to the leftmost sibling of the current focus.

iex> zipper = zipper |> Xipper.down |> Xipper.right |> Xipper.right
iex> Xipper.focus(zipper)
[3 ,4]
iex> zipper = Xipper.leftmost(zipper)
iex> Xipper.focus(zipper)
1
Link to this function lefts(xipper)
lefts(Xipper.t()) :: [any()]

Returns all left-hand siblings of a node.

Example

iex> zipper |> Xipper.down |> Xipper.lefts
[]

iex> zipper |> Xipper.down |> Xipper.rightmost |> Xipper.lefts
[1, 2, [3, 4]]
Link to this function make_node(zipper, node, children)
make_node(Xipper.t(), any(), [any()]) :: any()

Takes a zipper, a node, and a list of child nodes and returns a new node constructed from the node and children via the user-defined make_node function passed in to Xipper.new/4.

In the case of our example zipper, since a list node’s children are simply the list itself, in this context this function will return the list of children passed as the third argument.

Example

iex> Xipper.make_node(zipper, [1,2,3], [4,5,6])
[4, 5, 6]
Link to this function new(root, is_branch_fn, children_fn, make_node_fn)

Creates a new zipper.

Creating a zipper requires four arguments. The first argument is the data structure to be traversed by the zipper, and the final three arguments are functions. In order, these functions are:

  1. a function that takes a node from the data structure and returns true if it is a branch node (that is, it has children or can have children), and false otherwise
  2. a function that takes a node from the data structure and returns its children if it is a branch node
  3. a function that takes a node and a list of children and returns a new node with those children

As an example, the following code returns a zipper for a nested list.

Example

iex> zipper = Xipper.new(
...>   [1, 2, [3, 4], 5],
...>   &is_list/1,
...>   fn node -> node end,
...>   fn _node, children -> children end
...> )
iex> zipper.focus
[1, 2, [3, 4], 5]

The given arguments are

  1. the root list
  2. a function for defining a branch node — in this case whether a node is a list
  3. a function for returing a node’s children — since a branch node is simply a list, this returns the node itself
  4. a function for creating a new node — since a branch is just a list of its children, this function returns the list of children as the new node
Link to this function next(zipper)
next(Xipper.t()) :: Xipper.t()

Moves to the next node in a depth-first walk through the zipper.

next/1 will attempt to move down/1, then right/1, and then seek back up the zipper until it finds a right-hand sibling to move to. Once it reaches the end of the walk it will return the root of the zipper indefinitely.

Example

iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
2
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
[3, 4]
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
3
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
4
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
5
iex> zipper = Xipper.next(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
Link to this function path(zipper)
path(Xipper.t()) :: [any()]
Link to this function prev(zipper)
prev(Xipper.t()) :: Xipper.t()

Moves to the previous node in a depth-first walk through the zipper.

prev/1 will attempt to move left/1, then recuse down through its left siblings children, then up/1, until it reaches the root of the zipper. Calling prev/1 on a zipper that has reached the end of its depth-first walk (for which is_end/1 returns true), will return the same zipper indefinitely.

Example

iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]
iex> zipper = zipper |> Xipper.down |> Xipper.rightmost
iex> Xipper.focus(zipper)
5
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
4
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
3
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
[3, 4]
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
2
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]

iex> zipper = zipper |> Xipper.down |> Xipper.rightmost |> Xipper.next
iex> zipper = Xipper.prev(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]

Removes the current focus of the zipper and shifts focus to where the previous node in a depth-first walk would be.

This function returns an error if trying to remove the root of the zipper.

Example

iex> Xipper.remove(zipper)
{:error, :remove_of_root}

iex> zipper = zipper |> Xipper.down |> Xipper.remove
iex> Xipper.focus(zipper)
[2, [3, 4], 5]

iex> zipper = zipper |> Xipper.down |> Xipper.right |> Xipper.remove
iex> Xipper.focus(zipper)
1
iex> Xipper.rights(zipper)
[[3, 4], 5]
Link to this function replace(zipper, new_focus)
replace(Xipper.t(), any()) :: Xipper.t()

Replaces the current focus with the node passed as the second argument.

Example

iex> zipper = Xipper.down(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.replace(zipper, 42)
iex> Xipper.focus(zipper)
42

Shifts the zipper’s focus to the sibling node directly to the right of the current focus.

This function returns an error tuple if the current focus is the rightmost of its siblings.

Example

iex> zipper = Xipper.down(zipper)
iex> zipper |> Xipper.right |> Xipper.focus
2
iex> zipper = Xipper.rightmost(zipper)
iex> Xipper.focus(zipper)
5
iex> Xipper.right(zipper)
{:error, :right_of_rightmost}
Link to this function rightmost(zipper)
rightmost(Xipper.t()) :: Xipper.t()

Moves focus to the rightmost sibling of the current focus.

iex> zipper = zipper |> Xipper.down |> Xipper.rightmost
iex> Xipper.focus(zipper)
5
Link to this function rights(xipper)
rights(Xipper.t()) :: [any()]

Returns all right-hand siblings of a node.

Example

iex> zipper |> Xipper.down |> Xipper.rights
[2, [3, 4], 5]
Link to this function root(zipper)
root(Xipper.t()) :: Xipper.t()

Traverses a zipper upwards to the root of the zipper.

This function has no effect if the current focus is already the root.

Example

iex> zipper = Xipper.root(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]

iex> zipper = zipper |> Xipper.down |> Xipper.right |> Xipper.right |> Xipper.down
iex> Xipper.focus(zipper)
3
iex> zipper = Xipper.root(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]

Shifts the zipper’s focus to the current focus’s parent.

This function returns an error if the current focus is the root of the zipper.

Example

iex> zipper = Xipper.down(zipper)
iex> Xipper.focus(zipper)
1
iex> zipper = Xipper.up(zipper)
iex> Xipper.focus(zipper)
[1, 2, [3, 4], 5]

iex> Xipper.up(zipper)
{:error, :up_from_root}