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:

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()
0

From 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)
true

For 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: 7

For 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: 7

The 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())
true

Note, 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

Specs

index() :: non_neg_integer()

Specs

t() :: t(term())

Specs

t(value)

Specs

value() :: term()

Link to this section Functions

Specs

append(t(val), val) :: t(val) when val: value()

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.

Link to this function

append_many(array, values)

View Source

Specs

append_many(t(val), [val]) :: t(val) when val: value()

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

default_value(t()) :: term()

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
Link to this function

duplicate(elem, n, opts \\ [])

View Source

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

fetch(t(val), index()) :: {:ok, val} | :error when val: value()

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 ArgumentError when index >= size for fixed-size arrays

  • raises an ArgumentError for negative indexes

    iex> 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

fix(t(val)) :: t(val) when val: value()

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

fixed?(t()) :: boolean()

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

get(t(val), index()) :: t(val) when val: value()

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
Link to this function

get_and_update(array, index, fun)

View Source

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

map(t(val), (val -> val)) :: t(val) when val: value()

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)

Link to this function

map_with_index(array, fun)

View Source

Specs

map_with_index(t(val), (val, index() -> val)) :: t(val) when val: value()

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", "", "", "", "", "", "", "", ""}}
Link to this function

new(enumerable, opts \\ [])

View Source

Specs

new(Enumerable.t(), keyword()) :: t()
new(val, non_neg_integer()) :: t(val) when val: value()
Link to this function

pop(array, index, default \\ :"A.Array.default_value")

View Source

Specs

pop(t(val), index(), val) :: {val, t(val)} when val: value()

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:

  • the provided default parameter for pop/3
  • the array default value for pop/2

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

relax(t(val)) :: t(val) when val: value()

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)
Link to this function

replace_at(array, index, value)

View Source

Specs

replace_at(t(val), index(), val) :: t(val) when val: value()

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

Link to this function

set(array, index, value)

View Source

Specs

set(t(val), index(), val) :: t(val) when val: value()

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

sparse_map(t(val), (val -> val)) :: t(val) when val: value()

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)

Link to this function

sparse_map_with_index(array, fun)

View Source

Specs

sparse_map_with_index(t(val), (val, index() -> val)) :: t(val) when val: value()

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

sparse_to_list(t(val)) :: [val] when val: value()

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

to_list(t(val)) :: [val] when val: value()

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]
Link to this function

update_at(array, index, fun)

View Source

Specs

update_at(t(val), index(), (val -> val)) :: t(val) when val: value()

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]>

Underlying erlang function: same as A.Array.set/3