Current section
Files
Jump to
Current section
Files
lib/array.ex
defmodule Array do
@behaviour Access
@moduledoc """
A wrapper module for Erlang's array.
"""
defstruct content: nil
@type t :: %__MODULE__{content: :array.array()}
@type index :: non_neg_integer
@type element :: any
@type opts :: opt | [opt]
@type opt :: {:fixed, boolean} | :fixed | {:default, any} | {:size, non_neg_integer} | non_neg_integer
@type orddict :: [{index, element}]
@doc """
Creates a new, extendible array with initial size zero.
The default value is the atom nil, not undefined.
"""
@spec new() :: t
def new() do
%Array{content: :array.new({:default, nil})}
end
@doc """
Creates a new fixed array according to the given options.
By default, the array is extendible and has initial size zero.
The default value is the atom nil, if not specified.
`options` is a single term or a list of terms, selected from the following:
* `n : non_neg_integer` or `{:size, n : non_neg_integer}`
* Specifies the initial size of the array; this also implies `{:fixed, true}`.
If `n` is not a nonnegative integer, the call raises `ArgumentError`.
* `:fixed` or `{:fixed, true}`
* Creates a fixed-size array.
* `{:fixed, false}`
* Creates an extendible (non fixed-size) array.
* `{:default, value}`
* Sets the default value for the array to `value`.
"""
@spec new(opts) :: t
def new(options) do
if is_list(options) do
%Array{content: :array.new([{:default, nil} | options])}
else
%Array{content: :array.new([{:default, nil}, options])}
end
end
@doc """
Check if two arrays are equal using ===.
"""
@spec equal?(t, t) :: boolean
def equal?(%Array{content: c1}, %Array{content: c2}) do
s1 = :array.size(c1)
s2 = :array.size(c2)
cond do
s1 != s2 -> false
s1 <= 0 -> true
true ->
Enumerable.reduce(Range.new(0, s1-1), {:cont, true}, fn(idx, _acc) ->
if :array.get(idx, c1) === :array.get(idx, c2) do
{:cont, true}
else
{:halt, false}
end
end) |> elem(1)
end
end
@doc """
Gets the value used for uninitialized entries.
"""
@spec default(t) :: any
def default(%Array{content: c}),
do: :array.default(c)
@doc """
Fixes the size of the array. This prevents it from growing automatically upon insertion.
"""
@spec fix(t) :: t
def fix(%Array{content: c} = arr),
do: %Array{arr | content: :array.fix(c)}
@doc """
Folds the elements of the array using the given function and initial accumulator value.
The elements are visited in order from the lowest index to the highest.
If `fun` is not a function, the call raises `ArgumentError`.
"""
@spec foldl(t, acc, (index, element, acc -> acc)) :: acc when acc: var
def foldl(%Array{content: c}, acc, fun),
do: :array.foldl(fun, acc, c)
@doc """
Folds the elements of the array right-to-left using the given function and initial accumulator value.
The elements are visited in order from the highest index to the lowest.
If `fun` is not a function, the call raises `ArgumentError`.
"""
@spec foldr(t, acc, (index, element, acc -> acc)) :: acc when acc: var
def foldr(%Array{content: c}, acc, fun),
do: :array.foldr(fun, acc, c)
@doc """
Equivalent to `from_list(list, nil)`.
"""
@spec from_list(list) :: t
def from_list(list),
do: %Array{content: :array.from_list(list, nil)}
@doc """
Converts a list to an extendible array.
`default` is used as the value for uninitialized entries of the array.
If `list` is not a proper list, the call raises `ArgumentError`.
"""
@spec from_list(list, any) :: t
def from_list(list, default),
do: %Array{content: :array.from_list(list, default)}
@doc """
Equivalent to `from_orddict(orddict, nil)`.
"""
@spec from_orddict(orddict) :: t
def from_orddict(orddict),
do: %Array{content: :array.from_orddict(orddict, nil)}
@doc """
Converts an ordered list of pairs `{index, value}` to a corresponding extendible array.
`default` is used as the value for uninitialized entries of the array.
If `orddict` is not a proper, ordered list of pairs whose first elements are nonnegative integers,
the call raises `ArgumentError`.
"""
@spec from_orddict(orddict, any) :: t
def from_orddict(orddict, default),
do: %Array{content: :array.from_orddict(orddict, default)}
@doc """
Converts an Erlang's array to an array.
All properties (size, elements, default value, fixedness) of the original array are preserved.
If `erl_arr` is not an Erlang's array, the call raises `ArgumentError`.
"""
@spec from_erlang_array(:array.array()) :: t
def from_erlang_array(erl_arr) do
if :array.is_array(erl_arr) do
%Array{content: erl_arr}
else
raise ArgumentError
end
end
@doc """
Gets the value of entry `idx`. If `idx` is not a nonnegative integer, or if the array has
fixed size and `idx` is larger than the maximum index, the call raises `ArgumentError`.
"""
@spec get(t, index) :: element
def get(%Array{content: c}, idx),
do: :array.get(idx, c)
@doc """
Returns `true` if `arr` appears to be an array, otherwise `false`.
Note that the check is only shallow; there is no guarantee that `arr` is a well-formed array
representation even if this function returns `true`.
"""
@spec is_array(t) :: boolean
def is_array(arr) do
case arr do
%Array{content: c} -> :array.is_array(c)
_ -> false
end
end
@doc """
Checks if the array has fixed size. Returns `true` if the array is fixed, otherwise `false`.
"""
@spec is_fix(t) :: boolean
def is_fix(%Array{content: c}),
do: :array.is_fix(c)
@doc """
Maps the given function onto each element of the array.
The elements are visited in order from the lowest index to the highest.
If `fun` is not a function, the call raises `ArgumentError`.
"""
@spec map(t, (index, element -> any)) :: t
def map(%Array{content: c} = arr, fun),
do: %Array{arr | content: :array.map(fun, c)}
@doc """
Makes the array resizable.
"""
@spec relax(t) :: t
def relax(%Array{content: c} = arr),
do: %Array{arr | content: :array.relax(c)}
@doc """
Resets entry `idx` to the default value for the array.
If the value of entry `idx` is the default value the array will be returned unchanged.
Reset will never change size of the array. Shrinking can be done explicitly by calling `resize/2`.
If `idx` is not a nonnegative integer, or if the array has fixed size and `idx` is
larger than the maximum index, the call raises `ArgumentError`.
"""
@spec reset(t, index) :: t
def reset(%Array{content: c} = arr, idx),
do: %Array{arr | content: :array.reset(idx, c)}
@doc """
Changes the size of the array to that reported by `sparse_size/1`.
If the given array has fixed size, the resulting array will also have fixed size.
"""
@spec resize(t) :: t
def resize(%Array{content: c} = arr),
do: %Array{arr | content: :array.resize(c)}
@doc """
Changes the size of the array.
If `size` is not a nonnegative integer, the call raises `ArgumentError`.
If the given array has fixed size, the resulting array will also have fixed size.
"""
@spec resize(t, non_neg_integer) :: t
def resize(%Array{content: c} = arr, size),
do: %Array{arr | content: :array.resize(size, c)}
@doc """
Sets entry `idx` of the array to `val`.
If `idx` is not a nonnegative integer, or if the array has fixed size and `idx` is
larger than the maximum index, the call raises `ArgumentError`.
"""
@spec set(t, index, element) :: t
def set(%Array{content: c} = arr, idx, val),
do: %Array{arr | content: :array.set(idx, val, c)}
@doc """
Gets the number of entries in the array.
Entries are numbered from 0 to `size(array)-1`; hence, this is also the index of
the first entry that is guaranteed to not have been previously set.
"""
@spec size(t) :: non_neg_integer
def size(%Array{content: c}),
do: :array.size(c)
@doc """
Folds the elements of the array using the given function and initial accumulator value,
skipping default-valued entries.
The elements are visited in order from the lowest index to the highest.
If `fun` is not a function, the call raises `ArgumentError`.
"""
@spec sparse_foldl(t, acc, (index, element, acc -> acc)) :: acc when acc: var
def sparse_foldl(%Array{content: c}, acc, fun),
do: :array.sparse_foldl(fun, acc, c)
@doc """
Folds the elements of the array right-to-left using the given function and initial accumulator value,
skipping default-valued entries.
The elements are visited in order from the highest index to the lowest.
If `fun` is not a function, the call raises `ArgumentError`.
"""
@spec sparse_foldr(t, acc, (index, element, acc -> acc)) :: acc when acc: var
def sparse_foldr(%Array{content: c}, acc, fun),
do: :array.sparse_foldr(fun, acc, c)
@doc """
Maps the given function onto each element of the array, skipping default-valued entries.
The elements are visited in order from the lowest index to the highest.
If `fun` is not a function, the call raises `ArgumentError`.
"""
@spec sparse_map(t, (element -> any)) :: t
def sparse_map(%Array{content: c} = arr, fun),
do: %Array{arr | content: :array.sparse_map(fun, c)}
@doc """
Gets the number of entries in the array up until the last non-default valued entry.
In other words, returns `idx+1` if `idx` is the last non-default valued entry in the array,
or zero if no such entry exists.
"""
@spec sparse_size(t) :: non_neg_integer
def sparse_size(%Array{content: c}),
do: :array.sparse_size(c)
@doc """
Converts the array to a list, skipping default-valued entries.
"""
@spec sparse_to_list(t) :: list
def sparse_to_list(%Array{content: c}),
do: :array.sparse_to_list(c)
@doc """
Converts the array to an ordered list of pairs `{index, value}`, skipping default-valued entries.
"""
@spec sparse_to_orddict(t) :: [{index, element}]
def sparse_to_orddict(%Array{content: c}),
do: :array.sparse_to_orddict(c)
@doc """
Converts the array to its underlying Erlang's array.
"""
@spec to_erlang_array(t) :: :array.array()
def to_erlang_array(%Array{content: c}),
do: c
@doc """
Converts the array to a list.
"""
@spec to_list(t) :: list
def to_list(%Array{content: c}),
do: :array.to_list(c)
@doc """
Converts the array to an ordered list of pairs `{index, value}`.
"""
@spec to_orddict(t) :: [{index, element}]
def to_orddict(%Array{content: c}),
do: :array.to_orddict(c)
@doc """
Access behavior `fetch/2` callback.
"""
def fetch(arr, idx) do
{:ok, get(arr, idx)}
end
@doc """
Access behavior `get/3` callback
"""
def get(arr, idx, value) do
if size(arr) < idx do
get(arr, idx)
else
value
end
end
@doc """
Access behavior `get_and_update/3` callback
"""
def get_and_update(arr, idx, fun) do
value = get(arr, idx)
case fun.(value) do
{get, update} -> {get, set(arr, idx, update)}
:pop -> {value, set(arr, idx, nil)}
end
end
@doc """
Access behavior `pop/2` callback
"""
def pop(arr, idx) do
if size(arr) < idx do
{get(arr, idx), set(arr, idx, nil)}
else
{nil, arr}
end
end
end
defimpl Enumerable, for: Array do
def count(arr), do: {:ok, Array.size(arr)}
def member?(_arr, _value), do: {:error, __MODULE__}
def reduce(%Array{content: c}, acc, fun) do
Enumerable.reduce(:array.to_list(c), acc, fun)
end
def slice(arr) do
slice_fun = fn (start, length) ->
for i <- start..(start + length - 1) do
Array.get(arr, i)
end
end
{:ok, Array.size(arr), slice_fun}
end
end
defimpl Collectable, for: Array do
def empty(_arr) do
Array.new()
end
def into(original) do
{[], fn
list, {:cont, x} -> [x | list]
list, :done -> Array.from_list(Array.to_list(original) ++ :lists.reverse(list))
_, :halt -> :ok
end}
end
end
defimpl Inspect, for: Array do
import Inspect.Algebra
def inspect(arr, opts) do
concat(["#Array<", to_doc(Array.to_list(arr), opts),
", fixed=", Atom.to_string(Array.is_fix(arr)),
", default=", inspect(Array.default(arr)),
">"])
end
end