Packages

An array-backed vector providing fast random access

Current section

Files

Jump to
array_vector lib vector.ex
Raw

lib/vector.ex

defmodule Vector do
@moduledoc """
Functions that work on vectors.
This datastructure wraps Erlang's `array` type for fast random
lookup and update to large collections.
The point of this module is not meant to be a 1:1 wrapper, but to be
a useful set of higher-level API operations.
Note that this module is implemented as a struct with an `array` field,
pointing to the underlying Erlang `array` implementation. This field
is private, so you should use the functions in this module
to perform operations.
"""
# this is not great, but `array()` is not available in Elixir
@type t :: %__MODULE__{array: :array.array()}
@type index :: integer
@type value :: term
@behaviour Access
defstruct array: :array.new()
@doc """
Constructs a array-backed vector
## Examples
iex> Vector.new()
#Vector<[]>
iex> Vector.new(Vector.new())
#Vector<[]>
"""
def new(), do: %__MODULE__{}
@doc """
Constructs an array-backed vector from an enumerable
## Examples
iex> Vector.new([1,2,3])
#Vector<[1, 2, 3]>
iex> Vector.new(%{a: 1, b: 2})
#Vector<[a: 1, b: 2]>
iex> Vector.new(0)
#Vector<[]>
# this is exposing a bit of implementation, but I am
# unsure how else to test it
iex> Vector.new(5).array
{:array, 5, 10, :undefined, 10}
"""
def new(%__MODULE__{} = vector), do: vector
def new(size) when is_integer(size) do
%__MODULE__{
array: :array.new([
{:size, size},
{:fixed, false},
{:default, :undefined}
])
}
end
def new(enumerable) do
array =
enumerable
|> Enum.to_list
|> new_from_list
%Vector{array: array}
end
defp new_from_list(list) when is_list(list) do
:array.from_list(list)
end
@doc """
Converts a `vector` to a list
## Examples
iex> Vector.to_list(Vector.new([1,2,3]))
[1,2,3]
"""
def to_list(%Vector{array: array}) do
:array.sparse_to_list(array)
end
@doc """
Returns the size of a vector
## Examples
iex> Vector.size(Vector.new())
0
iex> Vector.size(Vector.new([1,2,3,4,5]))
5
"""
def size(%Vector{array: array}) do
:array.sparse_size(array)
end
@doc """
Checks if `vector` contains `value`
## Examples
iex> Vector.member?(Vector.new([1,2,3]), 99)
false
iex> Vector.member?(Vector.new([1,2,3]), 2)
true
"""
def member?(%Vector{array: array}, value) do
array
|> :array.sparse_to_list
|> MapSet.new()
|> MapSet.member?(value)
end
@doc """
Finds the element at the given `index` (zero-based) in logarithmic time.
Returns `{:ok, element}` if found, otherwise `:error`.
A negative index can be passed, in which case the index is counted from the end (e.g. -1 finds the last element).
## Examples
iex> Vector.fetch(Vector.new([1,2,3]), 9)
:error
iex> Vector.fetch(Vector.new([1,2,3]), 2)
{:ok, 3}
iex> Vector.fetch(Vector.new([1,2,3,4,5]), -2)
{:ok, 4}
"""
def fetch(%Vector{} = vector, index) when index < 0 do
fetch(vector, Vector.size(vector) + index)
end
@spec fetch(t, index) :: {:ok, value} | :error
def fetch(%Vector{array: array}, index) do
case :array.get(index, array) do
:undefined -> :error
value -> {:ok, value}
end
end
@doc """
Finds the element at the given `index` (zero-based) in logarithmic time.
Raises `OutOfBoundsError` if the given `index` is outside the range of
the enumerable.
A negative index can be passed, in which case the index is counted from the end (e.g. -1 finds the last element).
## Examples
iex> Vector.fetch!(Vector.new([1,2,3]), 1)
2
iex> Vector.fetch!(Vector.new([1,2,3]), -1)
3
iex> Vector.fetch!(Vector.new([1,2,3]), 99)
** (Enum.OutOfBoundsError) out of bounds error
"""
@spec fetch!(t, index) :: value | no_return
def fetch!(%Vector{} = vector, index) do
case fetch(vector, index) do
{:ok, value} -> value
:error -> raise Enum.OutOfBoundsError
end
end
@doc """
Puts the given value under index in vector in logarithmic time
A negative index can be passed, in which case the index is counted from the end (e.g. -1 finds the last element).
## Examples
iex> Vector.put(Vector.new([1,2,3]), 3, 99)
#Vector<[1, 2, 3, 99]>
iex> Vector.put(Vector.new([1,2,3]), 0, 3)
#Vector<[3, 2, 3]>
iex> Vector.put(Vector.new([1,2,3]), -1, 101)
#Vector<[1, 2, 101]>
"""
@spec put(t, index, value) :: t
def put(%Vector{} = vector, index, value) when index < 0 do
put(vector, Vector.size(vector) + index, value)
end
def put(%Vector{array: array}, index, value) do
%Vector{array: :array.set(index, value, array)}
end
@doc """
Updates the value in vector with the given function in logarithmic time.
If index is present in vector with value, fun is invoked with
argument value and its result is used as the new value of index. If index is
not present in vector, initial is inserted as the value of index.
A negative index can be passed, in which case the index is counted from the end (e.g. -1 finds the last element).
## Examples
iex> Vector.update(Vector.new([1,2,3]), 0, 13, &(&1 * 2))
#Vector<[2, 2, 3]>
iex> Vector.update(Vector.new([1,2,3]), 5, 11, &(&1 * 2))
#Vector<[1, 2, 3, 11]>
iex> Vector.update(Vector.new([1,2,3]), -2, 11, &(&1 * 2))
#Vector<[1, 4, 3]>
"""
@spec update(t, index, value, (value -> value)) :: t
def update(vector, index, initial, fun) when is_function(fun, 1) do
case fetch(vector, index) do
{:ok, value} ->
put(vector, index, fun.(value))
:error ->
put(vector, index, initial)
end
end
@doc """
Updates index with the given function in logarithmic time.
If index is present in map with value value, fun is invoked with argument value
and its result is used as the new value of index. If index is not present in map,
a Enum.OutOfBoundsError exception is raised.
## Examples
iex> Vector.update!(Vector.new([1,2,3]), 0, &(&1 * 2))
#Vector<[2, 2, 3]>
iex> Vector.update!(Vector.new([1,2,3]), 5, &(&1 * 2))
** (Enum.OutOfBoundsError) out of bounds error
iex> Vector.update!(Vector.new([1,2,3]), -2, &(&1 * 2))
#Vector<[1, 4, 3]>
"""
@spec update!(t, index, (value -> value)) :: t | no_return
def update!(vector, index, fun) when is_function(fun, 1) do
case fetch(vector, index) do
{:ok, value} ->
put(vector, index, fun.(value))
:error ->
raise Enum.OutOfBoundsError
end
end
@doc """
Gets the value at an index in logarithmic time, returning a default if it does not exist.
## Examples
iex> Vector.get(Vector.new([1,2,3]), 0)
1
iex> Vector.get(Vector.new([1,2,3]), 9)
nil
iex> Vector.get(Vector.new([1,2,3]), 9, 1000)
1000
"""
@spec get(t, index, default :: value) :: value
def get(vector, index, default \\ nil) do
case fetch(vector, index) do
{:ok, value} -> value
:error -> default
end
end
@doc """
Returns either the vector without the value at index, or the original vector
if no value is present at index.
## Examples
iex> Vector.delete(Vector.new([1,2,3,4,5]), 2)
#Vector<[1, 2, 4, 5]>
iex> Vector.delete(Vector.new([1,2,3]), 9)
#Vector<[1, 2, 3]>
"""
@spec delete(t, index) :: t
def delete(%Vector{array: array} = _vector, index) do
%Vector{array: :array.reset(index, array)}
end
@doc """
Gets and updates a value at the same time.
## Examples
iex> {val, new_vector} = Vector.get_and_update(Vector.new([1,2,3]), 0, fn(value) -> {value, value + 100} end)
iex> {val, Vector.to_list(new_vector)}
{1, [101, 2, 3]}
iex> {val, new_vector} = Vector.get_and_update(Vector.new([1,2,3]), 0, fn(_) -> :pop end)
iex> {val, Vector.to_list(new_vector)}
{1, [2, 3]}
"""
@spec get_and_update(t, index, (value -> {value, value} | :pop)) :: {value, t}
def get_and_update(vector, index, fun) do
case fun.(get(vector, index)) do
{value_to_return, new_value} -> {value_to_return, put(vector, index, new_value)}
:pop -> pop(vector, index)
end
end
@doc """
Deletes a value at index, returning the new vector and the value that was deleted
## Examples
iex> {value, new} = Vector.pop(Vector.new([1,2,3]), 2)
iex> {value, Vector.to_list(new)} # arrays cannot be checked for value equality
{3, [1, 2]}
iex> {value, new} = Vector.pop(Vector.new([1,2,3]), 99)
iex> {value, Vector.to_list(new)}
{nil, [1, 2, 3]}
"""
def pop(vector, index, default \\ nil) do
value = get(vector, index, default)
{value, delete(vector, index)}
end
@doc """
Append an item to to a vector
## Examples
iex> Vector.new([1, 2, 3]) |> Vector.append(9)
Vector.new([1, 2, 3, 9])
iex> Vector.new() |> Vector.append("hi")
Vector.new(["hi"])
iex> Vector.new([1, 2, 3]) |> Vector.append(9) |> Vector.size
4
"""
@spec append(t, value) :: t
def append(%__MODULE__{} = vector, value) do
size = Vector.size(vector)
Vector.put(vector, size, value)
end
@doc """
Reverse a vector.
## Examples
iex> vector = Vector.new([1,2,3])
iex> vector == vector |> Vector.reverse |> Vector.reverse
true
iex> Vector.reverse(Vector.new())
#Vector<[]>
iex> Vector.reverse(Vector.new([1, 2, 3]))
#Vector<[3, 2, 1]>
"""
def reverse(%__MODULE__{} = vector) do
%{vector | array: :array.from_list(Enum.reverse(vector))}
end
@doc """
Map a vector.
## Examples
iex> Vector.new() |> Vector.map(fn(_i, val) -> val + 1 end)
#Vector<[]>
iex> Vector.new([1, 2, 3]) |> Vector.map(fn(_i, val) -> val + 1 end)
#Vector<[2, 3, 4]>
"""
def map(%__MODULE__{array: array} = vector, fun) when is_function(fun, 2) do
%{vector | array: :array.sparse_map(fun, array)}
end
defimpl Enumerable do
def count(vector), do: {:ok, Vector.size(vector)}
def member?(vector, val), do: {:ok, Vector.member?(vector, val)}
def reduce(vector, acc, fun), do: Enumerable.List.reduce(Vector.to_list(vector), acc, fun)
end
defimpl Collectable do
def into(original) do
{original, fn
vector, {:cont, value} -> Vector.put(vector, Vector.size(vector) + 1, value)
vector, :done -> vector
_, :halt -> :ok
end}
end
end
defimpl Inspect do
import Inspect.Algebra
def inspect(set, opts) do
concat(["#Vector<", Inspect.List.inspect(Vector.to_list(set), opts), ">"])
end
end
end