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
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
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
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: []}
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]}
Specs
delete(ZipperList.t) :: ZipperList.t
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: []}
Specs
empty :: ZipperList.t
Returns an empty ZipperList with the cursor position at the front.
Examples
iex> ZipperList.empty
%ZipperList{left: [], cursor: nil, right: []}
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
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
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]}
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
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]}
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]}
Specs
left(ZipperList.t) :: ZipperList.t
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]}
Specs
pop(ZipperList.t) :: ZipperList.t
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: []}
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: []}
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: []}
Specs
reverse(ZipperList.t) :: ZipperList.t
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: []}
Specs
right(ZipperList.t) :: ZipperList.t
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: []}
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}
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]