zipper_list v1.1.1 ZipperList

A Haskell-inspired zipper list implementation.

A ZipperList allows for rapid left and right traversal on a list in constant time. Useful for cases where a simple enumeration won’t work. If you have to use Enum.at/2 repeatedly, consider using ZipperList instead.

Usage

The value at the current position is stored in %ZipperList{cursor}, and the items to the left and write are in %ZipperList{left} and %ZipperList{right} like:

iex> zip = %ZipperList{left: [3, 2, 1], cursor: 4, right: [5, 6]}
iex> zip |> ZipperList.right
%ZipperList{left: [4, 3, 2, 1], cursor: 5, right: [6]}
iex> zip |> ZipperList.left |> ZipperList.left
%ZipperList{left: [1], cursor: 2, right: [3, 4, 5, 6]}

Using with Enum

ZipperList implements the Enumerable protocol, so all Enum methods will work with ZipperList. However, all functions start enumerating at the cursor position. If you need to enumerate the whole zipper, use ZipperList.cursor_start/1 to reset it.

iex> zip = ZipperList.from_lists([1, 2, 3], [4, 5, 6])
iex> Enum.map(zip, fn(z) -> z.cursor * 3 end)
[12, 15, 18]

iex> zip = ZipperList.from_lists([1, 2, 3], [4, 5, 6])
iex> Enum.map(ZipperList.cursor_start(zip), fn(z) -> z.cursor * 3 end)
[3, 6, 9, 12, 15, 18]

Accessing data in the zipper

If you choose to directly access %ZipperList{left} keep in mind that it is stored in reverse:

iex> ZipperList.from_lists([1, 2, 3], [4, 5, 6])
%ZipperList{left: [3, 2, 1], cursor: 4, right: [5, 6]}

iex> ZipperList.to_list(%ZipperList{left: [3, 2, 1], cursor: 4, right: [5, 6]})
[1, 2, 3, 4, 5, 6]

Reference

See the Wikipidia article “Zipper_data structure” for more (mathematically complicated) information.

Summary

Functions

Returns true if the zipper is at the start

Returns the count of the number of elements in the zipper, including the cursor

Returns the zipper with the cursor set to the end. cursor will be nil. O(1)

Returns the zipper with the cursor set to the start. O(1)

Drops the value in the cursor and replaces it with the next value from the right

Returns an empty ZipperList with the cursor position at the front

Returns true if the zipper is empty

Returns true if the zipper is at the end

Returns a zipper containing the elements of xs, with the cursor at the first element

Returns a zipper containing the elements of xs, focused just off the right end of the list

Returns a new ZipperList with the cursor from ZipperList.right’s first element

Inserts value at the cursor position, pushing the current cursor and all values to the right

Returns the zipper with the cursor focus shifted one element to the left, or zipper if the cursor is already at the beginning

Pops a value off of the position before the cursor. If there are no values to the left of the cursor, it returns zipper

Pushes a value into the position before the cursor, leaving the cursor unchanged

Replaces the zipper’s cursor with the passed in value. If there is no current cursor, the value becomes the new cursor

Returns the zipper with the elements in the reverse order. O(1)

Returns the zipper with the cursor focus shifted one element to the right, or returns zipper if the cursor is past the end

Returns the cursor or returns the default option if the cursor is nil

Returns a list from the zipper

Types

t :: %ZipperList{cursor: any, left: list, right: list}

Functions

beginning?(zipper_list)

Specs

beginning?(ZipperList.t) :: boolean

Returns true if the zipper is at the start.

Examples

iex> ZipperList.beginning?(%ZipperList{left: [], cursor: 0, right: [1, 2, 3]})
true

iex> ZipperList.beginning?(%ZipperList{left: [2, 1], cursor: 3, right: [4]})
false
count(zipper_list)

Specs

count(ZipperList.t) :: integer

Returns the count of the number of elements in the zipper, including the cursor.

Examples

iex> ZipperList.count(%ZipperList{left: [2, 1], cursor: 3, right: [4, 5]})
5
cursor_end(zipper_list)

Specs

cursor_end(ZipperList.t) :: ZipperList.t

Returns the zipper with the cursor set to the end. cursor will be nil. O(1)

Examples

iex> ZipperList.cursor_end(%ZipperList{left: [2, 1], cursor: 3, right: [4, 5]})
%ZipperList{left: [5, 4, 3, 2, 1], cursor: nil, right: []}
cursor_start(zipper_list)

Specs

cursor_start(ZipperList.t) :: ZipperList.t

Returns the zipper with the cursor set to the start. O(1)

Examples

iex> ZipperList.cursor_start(%ZipperList{left: [2, 1], cursor: 3, right: [4]})
%ZipperList{left: [], cursor: 1, right: [2, 3, 4]}
delete(zipper_list)

Specs

Drops the value in the cursor and replaces it with the next value from the right.

Examples

iex> ZipperList.delete(%ZipperList{left: [3], cursor: 4, right: [5, 2]})
%ZipperList{left: [3], cursor: 5, right: [2]}

iex> ZipperList.delete(ZipperList.empty)
%ZipperList{left: [], cursor: nil, right: []}

If there is no value to the right, cursor will be nil:

iex> ZipperList.delete(%ZipperList{left: [2, 5, 3], cursor: 8, right: []})
%ZipperList{left: [2, 5, 3], cursor: nil, right: []}
empty()

Specs

empty :: ZipperList.t

Returns an empty ZipperList with the cursor position at the front.

Examples

iex> ZipperList.empty
%ZipperList{left: [], cursor: nil, right: []}
empty?(zipper_list)

Specs

empty?(ZipperList.t) :: boolean

Returns true if the zipper is empty.

Examples

iex> ZipperList.empty?(ZipperList.empty)
true

iex> ZipperList.empty?(%ZipperList{left: [3, 2, 1], cursor: 4})
false
end?(zipper_list)

Specs

end?(ZipperList.t) :: boolean

Returns true if the zipper is at the end.

Examples

iex> ZipperList.end?(%ZipperList{left: [3, 2, 1], cursor: nil, right: []})
true

iex> ZipperList.end?(%ZipperList{left: [2, 1], cursor: 3, right: [4]})
false
from_list(list)

Specs

from_list(list) :: ZipperList.t

Returns a zipper containing the elements of xs, with the cursor at the first element.

Examples

iex> ZipperList.from_list([1, 2, 3])
%ZipperList{left: [], cursor: 1, right: [2, 3]}
from_list_end(xs)

Specs

from_list_end(list) :: ZipperList.t

Returns a zipper containing the elements of xs, focused just off the right end of the list.

Examples

iex> zip = ZipperList.from_list_end([1, 2, 3])
%ZipperList{left: [3, 2, 1], cursor: nil, right: []}
iex> ZipperList.end? zip
true
from_lists(left, list)

Specs

from_lists(list, list) :: ZipperList.t

Returns a new ZipperList with the cursor from ZipperList.right’s first element.

Examples

iex> ZipperList.from_lists([1, 2, 3], [4, 5])
%ZipperList{left: [3, 2, 1], cursor: 4, right: [5]}
insert(zipper_list, value)

Specs

insert(ZipperList.t, any) :: ZipperList.t

Inserts value at the cursor position, pushing the current cursor and all values to the right.

Examples

Inserting a value replaces the cursor:

iex> ZipperList.insert(%ZipperList{left: [1], cursor: 2, right: [3]}, 5)
%ZipperList{left: [1], cursor: 5, right: [2, 3]}

On empty zippers, it inserts at the cursor position:

iex> ZipperList.insert(ZipperList.empty, 5)
%ZipperList{left: [], cursor: 5, right: []}

Any values are pushed to the right:

iex> ZipperList.insert(%ZipperList{left: [], cursor: 5, right: []}, 10)
%ZipperList{left: [], cursor: 10, right: [5]}
left(zipper_list)

Specs

Returns the zipper with the cursor focus shifted one element to the left, or zipper if the cursor is already at the beginning.

Use ZipperList.begin? to check if the zipper is at the beginning.

Note: Moving left will trim nil if right is empty.

Examples

iex> ZipperList.left(%ZipperList{left: [2, 1], cursor: 3, right: [4]})
%ZipperList{left: [1], cursor: 2, right: [3, 4]}

iex> ZipperList.left(%ZipperList{left: [], cursor: 1, right: [2, 3]})
%ZipperList{left: [], cursor: 1, right: [2, 3]}
pop(zipper_list)

Specs

Pops a value off of the position before the cursor. If there are no values to the left of the cursor, it returns zipper.

Examples

iex> ZipperList.pop(%ZipperList{left: [1], cursor: 2, right: [3, 4]})
%ZipperList{left: [], cursor: 2, right: [3, 4]}

iex> ZipperList.pop(ZipperList.empty)
%ZipperList{left: [], cursor: nil, right: []}
push(zipper_list, value)

Specs

push(ZipperList.t, any) :: ZipperList.t

Pushes a value into the position before the cursor, leaving the cursor unchanged.

Examples

iex> ZipperList.push(%ZipperList{left: [1], cursor: 2, right: [3, 4]}, 5)
%ZipperList{left: [5, 1], cursor: 2, right: [3, 4]}
iex> ZipperList.push(ZipperList.empty, 5)
%ZipperList{left: [5], cursor: nil, right: []}
replace(zipper_list, value)

Specs

replace(ZipperList.t, any) :: ZipperList.t

Replaces the zipper’s cursor with the passed in value. If there is no current cursor, the value becomes the new cursor.

Examples

iex> ZipperList.replace(%ZipperList{left: [1], cursor: 2, right: [3, 4]}, 5)
%ZipperList{left: [1], cursor: 5, right: [3, 4]}

iex> ZipperList.replace(ZipperList.empty, 5)
%ZipperList{left: [], cursor: 5, right: []}
reverse(zipper_list)

Specs

Returns the zipper with the elements in the reverse order. O(1).

The cursor “position” is shifted, but the value does not change. If the cursor was at the start, it’s now at the end, and if it was at the end, it’s now at the start.

Examples

iex> ZipperList.reverse(%ZipperList{left: [2, 1], cursor: 3, right: [4]})
%ZipperList{left: [4], cursor: 3, right: [2, 1]}

iex> ZipperList.reverse(%ZipperList{left: [], cursor: 1, right: [2, 3, 4]})
%ZipperList{left: [2, 3, 4], cursor: 1, right: []}
right(zipper_list)

Specs

Returns the zipper with the cursor focus shifted one element to the right, or returns zipper if the cursor is past the end.

Use ZipperList.end? to check if the zipper is at the end.

Examples

The cursor moves out of the right list:

iex> ZipperList.right(%ZipperList{left: [2, 1], cursor: 3, right: [4, 5]})
%ZipperList{left: [3, 2, 1], cursor: 4, right: [5]}

If it is on the last item, cursor will be nil.

iex> ZipperList.right(%ZipperList{left: [3, 2, 1], cursor: 4, right: []})
%ZipperList{left: [4, 3, 2, 1], cursor: nil, right: []}

If the cursor is at the end, right returns the zipper:

iex> ZipperList.right(%ZipperList{left: [4, 3, 2, 1], cursor: nil, right: []})
%ZipperList{left: [4, 3, 2, 1], cursor: nil, right: []}
safe_cursor(zipper_list, default)

Specs

safe_cursor(ZipperList.t, any) :: ZipperList.t

Returns the cursor or returns the default option if the cursor is nil.

Examples

iex> ZipperList.safe_cursor(ZipperList.empty, 5)
%ZipperList{left: [], cursor: 5, right: []}

iex> ZipperList.safe_cursor(%ZipperList{cursor: 10}, 5)
%ZipperList{cursor: 10}
to_list(zipper_list)

Specs

to_list(ZipperList.t) :: list

Returns a list from the zipper.

Examples

iex> ZipperList.to_list(%ZipperList{left: [3,2,1], cursor: 4, right: [5,6]})
[1, 2, 3, 4, 5, 6]