A.Array (Aja v0.1.2) View Source
A wrapper of erlang's :array module offering an
elixir-friendly API and interoperability.
Arrays are data structures able to, unlilke lists:
- access the i-th element (get/set) in constant time
- access the size in constant time
Arrays are often the go-to data structure in imperative languages, they are however not so easy to work with in functional languages like Elixir. Arrays cannot be:
- efficiently built recursively while staying immutable
- pattern matched upon
- easily compared...
For these reasons, lists should still be the go-to data structure for most use cases. But some algorithms might justify the use of arrays. If you think you are in such a case, make sure to benchmark it first to confirm.
A.Array aims at simplifying working with arrays from elixir over erlang's :array.
It adds:
- implementation of the
Inspect,Enumerable,Collectableprotocols - implementation of the
Accessbehaviour - optionally implements the
Jason.Encoderprotocol ifJasonis installed - an API more consistent with elixir standard library
- pipe-operator friendliness
- more user-friendly error messages
- convenience functions to append:
A.Array.append/2andA.Array.append_many/2
An array can be constructed using A.Array.new/0:
iex> A.Array.new()
#A.Array<[]>Elements in an array don't have to be of the same type and they can be
populated from an enumerable using A.Array.new/1:
iex> A.Array.new([1, :two, {"three"}])
#A.Array<[1, :two, {"three"}]>Dynamic size, resizing
Arrays can be sparse and will automatically grow if needed:
iex> A.Array.new(1..3) |> A.Array.set(7, 45)
#A.Array<[1, 2, 3, nil, nil, nil, nil, 45]>They are populated by the default value (nil by default):
iex> A.Array.new(1..3, default: 0) |> A.Array.set(7, 45)
#A.Array<[1, 2, 3, 0, 0, 0, 0, 45], default: 0>
iex> A.Array.new(1..3, default: 0) |> A.Array.default_value()
0From the original documentation:
There is no difference between an unset entry and an entry which has been explicitly set to the same value as the default one. If you need to differentiate between unset and set entries, you must make sure that the default value cannot be confused with the values of set entries.
Fixed-size arrays
Arrays can be of fixed size to avoid growing or accessing out-of-bounds elements.
They can be directly created with the fixed? option, or fixed later by invoking A.Array.fix/1.
iex> fixed = A.Array.new(1..3, fixed?: true)
#A.Array<[1, 2, 3], fixed?: true>
iex> ^fixed = A.Array.new(1..3) |> A.Array.fix()
#A.Array<[1, 2, 3], fixed?: true>
iex> A.Array.fixed?(fixed)
trueFor fixed-size arrays, read or write access through an index must always be below its size.
iex> A.Array.new(1..3, fixed?: true) |> A.Array.set(7, 45)
** (ArgumentError) cannot access index above fixed size, expected index < 3, got: 7For read access as well:
iex> A.Array.new(1..3, fixed?: true) |> A.Array.get(7)
** (ArgumentError) cannot access index above fixed size, expected index < 3, got: 7The opposite operation is A.Array.relax/1:
iex> relaxed = A.Array.new(1..3, fixed?: true) |> A.Array.relax()
#A.Array<[1, 2, 3]>
iex> A.Array.fixed?(relaxed)
false
iex> A.Array.set(relaxed, 7, 45)
#A.Array<[1, 2, 3, nil, nil, nil, nil, 45]>Access behaviour
A.Array implements the Access behaviour.
iex> array = A.Array.new(1..5)
iex> array[1]
2
iex> put_in(array[2], "updated")
#A.Array<[1, 2, "updated", 4, 5]>
iex> {4, updated} = pop_in(array[3])
iex> updated
#A.Array<[1, 2, 3, nil, 5]>
With Jason
iex> A.Array.new(1..5) |> A.Array.set(9, 10) |> Jason.encode!()
"[1,2,3,4,5,null,null,null,null,10]"Pattern-match and opaque type
An A.Array is represented internally using the %A.Array{} struct. This struct
can be used whenever there's a need to pattern match on something being an A.Array:
iex> match?(%A.Array{}, A.Array.new())
trueNote, however, than A.Array is an opaque type:
its struct internal fields must not be accessed directly.
Use the functions in this module to perform operations on arrays, or the Enum module.
Link to this section Summary
Functions
Appends a value at the end of the array.
Appends all values from the enumerable at the end of the array.
Returns the default value of array.
Returns an array with elem repeated n times.
Fetches the value for a specific index and returns it in a ok-tuple.
If the key does not exist, returns :error.
Ensure array has a fixed size.
Returns true if array is fixed size, false else.
Returns the i-th element in array.
Gets the value from index and updates it, all in one pass.
Returns an array where each element is the result of invoking fun on each corresponding element.
Same as A.Array.map/2 but takes an arity /2 callback looping over (value, index).
Returns a new empty array.
Creates an array from an enumerable.
Returns the value for index and resets the existing value to the array default.
It returns a tuple where the first element is the value for index and the
second element is the array with the reset value.
If the index is not present in the array, {default, array} is returned, where default is
Ensure array has a dynamic (non-fixed) size.
Similar to A.Array.set/3, excepts it does nothing for out of bound indexes.
Returns a new array where the i-th element is su array.
Returns the number of elements in array.
Same as A.Array.map/2 but keeps the sparse elements untouched.
Same as A.Array.sparse_map/2 but takes an arity /2 callback looping over (value, index).
Converts array to a list keeping only non-sparse values.
Converts array to a list.
Returns an array with an updated value at the specified index by invoking fun.
Link to this section Types
Link to this section Functions
Specs
Appends a value at the end of the array.
Some append might trigger resizes: if you need to append several values,
use append_many/2 which only does one resize.
Examples
iex> A.Array.new([1, 2, 3]) |> A.Array.append(4)
#A.Array<[1, 2, 3, 4]>Not directly in the original erlang module, based on :array.set/3.
Specs
Appends all values from the enumerable at the end of the array.
It should be more efficient than many individual calls to append/2
since it only needs to resize once.
Examples
iex> A.Array.new([1, 2, 3]) |> A.Array.append_many([4, 5, 6])
#A.Array<[1, 2, 3, 4, 5, 6]>Not directly in the original erlang module, based on :array.set/3.
Specs
Returns the default value of array.
Examples
iex> A.Array.default_value(A.Array.new([]))
nil
iex> A.Array.default_value(A.Array.new([], default: 0))
0
Underlying erlang function: :array.default/1
iex> :array.default(:array.from_list([]))
:undefined
iex> :array.default(:array.from_list([], 0))
0
Returns an array with elem repeated n times.
Sets elem as the default value.
Mirroring List.duplicate/2.
Examples
iex> A.Array.duplicate(0, 9)
#A.Array<[0, 0, 0, 0, 0, 0, 0, 0, 0], default: 0>
iex> A.Array.duplicate("hi", 3, fixed?: true)
#A.Array<["hi", "hi", "hi"], default: "hi", fixed?: true>
Underlying erlang function: :array.new/1
iex> :array.new(size: 9, default: 0)
{:array, 9, 0, 0, 10}
iex> :array.new(size: 3, default: "hi", fixed: false)
{:array, 3, 10, "hi", 10}Note: in the erlang version, the array is fixed size by default.
Specs
Fetches the value for a specific index and returns it in a ok-tuple.
If the key does not exist, returns :error.
Examples
iex> A.Array.new([1, 2, 3]) |> A.Array.fetch(2)
{:ok, 3}
iex> A.Array.new([1, 2, 3]) |> A.Array.fetch(3)
:error
iex> A.Array.new([1, 2, 3], fixed?: true) |> A.Array.fetch(3)
:error
Underlying erlang function: :array.get/2
Unlike A.Array.fetch/2 which treats all out of bound cases the same,:array.get/2:
returns the default value when index >= size for non-fixed arrays
raises an
ArgumentErrorwhen index >= size for fixed-size arraysraises an
ArgumentErrorfor negative indexesiex> array = :array.from_list([1, 2, 3]) iex> :array.get(2, array) 3 iex> :array.get(3, array) :undefined iex> :array.get(3, :array.fix(array)) ** (ArgumentError) argument error
Specs
Ensure array has a fixed size.
Does nothing if it is already the case.
Calls to A.Array.get/2 or A.Array.set/3 with index >= size will fail for a fixed-sized array.
The reverse operation is A.Array.relax/1.
Examples
iex> fixed = A.Array.fix(A.Array.new([1, 2, 3]))
#A.Array<[1, 2, 3], fixed?: true>
iex> A.Array.fixed?(fixed)
true
iex> ^fixed = A.Array.fix(fixed)
iex> A.Array.get(fixed, 3)
** (ArgumentError) cannot access index above fixed size, expected index < 3, got: 3
Underlying erlang function: :array.fix/1
iex> fixed = :array.fix(:array.from_list([1, 2, 3]))
iex> :array.is_fix(fixed)
true
iex> ^fixed = :array.fix(fixed)
Specs
Returns true if array is fixed size, false else.
Examples
iex> A.Array.fixed?(A.Array.new([1, 2, 3]))
false
iex> A.Array.fixed?(A.Array.new([1, 2, 3], fixed?: true))
true
Underlying erlang function: :array.is_fix/1
iex> array = :array.from_list([1, 2, 3])
iex> :array.is_fix(array)
false
iex> :array.is_fix(:array.fix(array))
true
Specs
Returns the i-th element in array.
Runs in constant time.
Examples
iex> array = A.Array.new([1, 2, 3, 5, 8])
iex> A.Array.get(array, 2)
3
iex> A.Array.get(array, 10)
nil
Underlying erlang function: :array.get/2
iex> array = :array.from_list([1, 2, 3, 5, 8])
iex> :array.get(2, array)
3
Specs
get_and_update(t(val), index(), (val -> {returned, val} | :pop)) :: {returned, t(val)} when val: value(), returned: term()
Gets the value from index and updates it, all in one pass.
This fun argument receives the value of index (or the default value
if key is not present) and must return a two-element tuple: the "get" value
(the retrieved value, which can be operated on before being returned)
and the new value to be stored under index. The fun may also
return :pop, implying the current value shall be reset to the default value
of the array and its previous value returned.
The returned value is a tuple with the "get" value returned by
fun and a new keyword list with the updated value under index.
Examples
iex> array = A.Array.new([1, 2, 3])
iex> {2, updated} = A.Array.get_and_update(array, 1, fn current_value ->
...> {current_value, :new_value}
...> end)
iex> updated
#A.Array<[1, :new_value, 3]>
iex> {nil, updated} = A.Array.get_and_update(array, 3, fn current_value ->
...> {current_value, :new_value}
...> end)
iex> updated
#A.Array<[1, 2, 3, :new_value]>
iex> {2, updated} = A.Array.get_and_update(array, 1, fn _ -> :pop end)
iex> updated
#A.Array<[1, nil, 3]>
iex> {nil, updated} = A.Array.get_and_update(array, 3, fn _ -> :pop end)
iex> updated
#A.Array<[1, 2, 3]>
Specs
Returns an array where each element is the result of invoking fun on each corresponding element.
Mirroring the behavior of Enum.map/2 but returns an A.Array instead of a list.
Examples
iex> array = A.Array.new([1, 2, 3, 5, 8])
iex> A.Array.map(array, &(&1 + 30))
#A.Array<[31, 32, 33, 35, 38]>
iex> sparse = A.Array.new([1, 2, 3], default: 0) |> A.Array.set(7, 10)
#A.Array<[1, 2, 3, 0, 0, 0, 0, 10], default: 0>
iex> A.Array.map(sparse, &(&1 + 30))
#A.Array<[31, 32, 33, 30, 30, 30, 30, 40], default: 0>See also: A.Array.map_with_index/2, A.Array.sparse_map/2 and A.Array.sparse_map_with_index/2
Underlying erlang function: :array.map/2
iex> array = :array.from_list([1, 2, 3, 5, 8])
iex> result = :array.map(fn _index, value -> 30 + value end, array)
iex> :array.to_list(result)
[31, 32, 33, 35, 38]Note: :array.map/2 takes a callback of arity /2 looping over (index, value)
Specs
Same as A.Array.map/2 but takes an arity /2 callback looping over (value, index).
Examples
iex> array = A.Array.new([1, 2, 3, 5, 8])
iex> A.Array.map_with_index(array, fn value, index -> {index, value} end)
#A.Array<[{0, 1}, {1, 2}, {2, 3}, {3, 5}, {4, 8}]>
iex> sparse = A.Array.new([1, 2, 3], default: 0) |> A.Array.set(7, 10)
#A.Array<[1, 2, 3, 0, 0, 0, 0, 10], default: 0>
iex> A.Array.map_with_index(sparse, fn value, index -> value + index end)
#A.Array<[1, 3, 5, 3, 4, 5, 6, 17], default: 0>See also: A.Array.map/2, A.Array.sparse_map/2 and A.Array.sparse_map_with_index/2
Underlying erlang function: :array.map/2
iex> array = :array.from_list([1, 2, 3, 5, 8])
iex> result = :array.map(fn index, value -> {index, value} end, array)
iex> :array.to_list(result)
[{0, 1}, {1, 2}, {2, 3}, {3, 5}, {4, 8}]
Specs
new() :: t()
Returns a new empty array.
Examples
iex> A.Array.new()
#A.Array<[]>
Creates an array from an enumerable.
Examples
iex> A.Array.new([:b, :a, 3])
#A.Array<[:b, :a, 3]>
iex> A.Array.new(1..7)
#A.Array<[1, 2, 3, 4, 5, 6, 7]>
iex> A.Array.new('hello', default: ?\s)
#A.Array<[104, 101, 108, 108, 111], default: 32>
iex> A.Array.new('hello', default: ?\s, fixed?: true)
#A.Array<[104, 101, 108, 108, 111], default: 32, fixed?: true>
Underlying erlang function: :array.from_list/1
iex> :array.from_list([1, 2, 3, 5, 8])
{:array, 5, 10, :undefined, {1, 2, 3, 5, 8, :undefined, :undefined, :undefined, :undefined, :undefined}}
iex> :array.from_list(["abc", "def"], "")
{:array, 2, 10, "", {"abc", "def", "", "", "", "", "", "", "", ""}}
Specs
new(Enumerable.t(), keyword()) :: t()
new(val, non_neg_integer()) :: t(val) when val: value()
Specs
Returns the value for index and resets the existing value to the array default.
It returns a tuple where the first element is the value for index and the
second element is the array with the reset value.
If the index is not present in the array, {default, array} is returned, where default is:
Examples
iex> array = A.Array.new([1, 2, 3])
iex> {2, updated} = A.Array.pop(array, 1)
iex> updated
#A.Array<[1, nil, 3]>
iex> {nil, updated} = A.Array.pop(array, 3)
iex> updated
#A.Array<[1, 2, 3]>
iex> {0, updated} = A.Array.pop(array, 3, 0)
iex> updated
#A.Array<[1, 2, 3]>
iex> {0, updated} = A.Array.new([1, 2, 3], default: 0) |> A.Array.pop(3)
iex> updated
#A.Array<[1, 2, 3], default: 0>
Specs
Ensure array has a dynamic (non-fixed) size.
Does nothing if it is already the case. The reverse operation is A.Array.fix/1.
Examples
iex> relaxed = A.Array.new([1, 2, 3], fixed?: true) |> A.Array.relax()
#A.Array<[1, 2, 3]>
iex> A.Array.fixed?(relaxed)
false
iex> ^relaxed = A.Array.relax(relaxed)
iex> A.Array.get(relaxed, 4)
nil
Underlying erlang function: :array.relax/1
iex> fixed = :array.fix(:array.from_list([1, 2, 3]))
iex> relaxed = :array.relax(fixed)
iex> :array.is_fix(relaxed)
false
iex> ^relaxed = :array.relax(relaxed)
Specs
Similar to A.Array.set/3, excepts it does nothing for out of bound indexes.
Mirroring the behavior of List.replace_at/3.
Examples
iex> array = A.Array.new([1, 2, 3, 5, 8])
iex> A.Array.replace_at(array, 2, 100)
#A.Array<[1, 2, 100, 5, 8]>
iex> A.Array.replace_at(array, 5, 100)
#A.Array<[1, 2, 3, 5, 8]>
Underlying erlang function: same as A.Array.set/3
Specs
Returns a new array where the i-th element is su array.
Runs in constant time.
Examples
iex> array = A.Array.new([1, 2, 3, 5, 8])
iex> A.Array.set(array, 2, 100)
#A.Array<[1, 2, 100, 5, 8]>Unless of fixed size, the array will grow automatically to accomodate the new index, using its default value:
iex> A.Array.new() |> A.Array.set(7, 45)
#A.Array<[nil, nil, nil, nil, nil, nil, nil, 45]>Also see: A.Array.replace_at/3, A.Array.update_at/3
Underlying erlang function: :array.set/3
iex> array = :array.from_list([1, 2, 3])
{:array, 3, 10, :undefined, {1, 2, 3, :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, :undefined}}
iex> :array.set(2, 100, array)
{:array, 3, 10, :undefined, {1, 2, 100, :undefined, :undefined, :undefined, :undefined, :undefined, :undefined, :undefined}}
Specs
size(t()) :: non_neg_integer()
Returns the number of elements in array.
Examples
iex> array = A.Array.new([1, 2, 3, 5, 8])
iex> A.Array.size(array)
5
Underlying erlang function: :array.size/1
iex> array = :array.from_list([1, 2, 3, 5, 8])
iex> :array.size(array)
5
Specs
Same as A.Array.map/2 but keeps the sparse elements untouched.
Examples
iex> sparse = A.Array.new([1, 2, 3], default: 0) |> A.Array.set(7, 10)
#A.Array<[1, 2, 3, 0, 0, 0, 0, 10], default: 0>
iex> A.Array.sparse_map(sparse, &(&1 + 30))
#A.Array<[31, 32, 33, 0, 0, 0, 0, 40], default: 0>See also: A.Array.map/2, A.Array.map_with_index/2 and A.Array.sparse_map_with_index/2
Underlying erlang function: :array.sparse_map/2
iex> array = :array.set(7, 10, :array.from_list([1, 2, 3]))
iex> result = :array.sparse_map(fn _index, value -> 30 + value end, array)
iex> :array.to_list(result)
[31, 32, 33, :undefined, :undefined, :undefined, :undefined, 40]Note: :array.sparse_map/2 takes a callback of arity /2 looping over (index, value)
Specs
Same as A.Array.sparse_map/2 but takes an arity /2 callback looping over (value, index).
Examples
iex> sparse = A.Array.new([1, 2, 3], default: 0) |> A.Array.set(7, 10)
#A.Array<[1, 2, 3, 0, 0, 0, 0, 10], default: 0>
iex> A.Array.sparse_map_with_index(sparse, fn value, index -> value + index end)
#A.Array<[1, 3, 5, 0, 0, 0, 0, 17], default: 0>See also: A.Array.map/2, A.Array.sparse_map/2 and A.Array.map_with_index/2
Underlying erlang function: :array.sparse_map/2
iex> array = :array.set(7, 10, :array.from_list([1, 2, 3]))
iex> result = :array.sparse_map(fn index, value -> index + value end, array)
iex> :array.to_list(result)
[1, 3, 5, :undefined, :undefined, :undefined, :undefined, 17]
Specs
Converts array to a list keeping only non-sparse values.
Examples
iex> A.Array.new([nil, 1, nil, nil, 2, 3, nil]) |> A.Array.sparse_to_list()
[1, 2, 3]
Underlying erlang function: :array.sparse_to_list/1
iex> :array.sparse_to_list(:array.from_list([:undefined, 1, 2, :undefined, 3]))
[1, 2, 3]
Specs
Converts array to a list.
Examples
iex> A.Array.new([1, 2, 3]) |> A.Array.to_list()
[1, 2, 3]
Underlying erlang function: :array.to_list/1
iex> :array.to_list(:array.from_list([1, 2, 3]))
[1, 2, 3]
Specs
Returns an array with an updated value at the specified index by invoking fun.
Does nothing for out of bound indexes.
Mirroring the behavior of List.update_at/3.
Examples
iex> array = A.Array.new([1, 2, 3, 5, 8])
iex> A.Array.update_at(array, 2, &(&1 + 100))
#A.Array<[1, 2, 103, 5, 8]>
iex> A.Array.update_at(array, 5, &(&1 + 100))
#A.Array<[1, 2, 3, 5, 8]>