Packages
iteraptor
1.13.0
1.15.0
1.14.0
1.13.1
1.13.0
1.12.2
1.12.1
1.12.0
1.11.0
1.10.3
1.10.2
1.10.1
1.9.0
1.8.0
1.7.3
1.7.2
1.7.1
1.7.0
1.6.1
1.6.0
1.4.1
1.4.0
1.3.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc1
0.7.0
0.6.2
0.6.1
0.5.2
0.5.1
0.5.0
0.4.0
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.4
0.2.3
0.2.2
0.2.1
0.1.1
0.1.0
This small library allows the deep iteration / mapping of Enumerables.
Current section
Files
Jump to
Current section
Files
lib/iteraptor/array.ex
defmodule Iteraptor.Array do
@moduledoc """
Array emulation implementing `Access` behaviour. Index in array is zero-based.
`Array` is the "go to" array data structure in Elixir. An array can be
constructed using `Array.new/{0,1}`:
iex> Iteraptor.Array.new()
#Array<[]>
iex> Iteraptor.Array.new(2)
#Array<[nil, nil]>
iex> Iteraptor.Array.new([:foo, :bar])
#Array<[:foo, :bar]>
An array can contain any kind of elements, and elements in an array don't have
to be of the same type. By definition, arrays have _keys_ in `0..size-1` range.
Arrays are implicitly expandable, which means adding an element at index `100`
to the array currently containing 1 element would increase the size of the
array to `100`.
iex> array = Iteraptor.Array.new([:foo])
iex> Iteraptor.Array.set(array, 3, :bar)
#Array<[:foo, nil, nil, :bar]>
An `Array` is represented internally using the `%Array{}` struct. Note that,
however, the struct fields are private and must not be accessed directly;
use the functions in this module to perform operations on arrays.
`Array`s can also be constructed starting from other collection-type data
structures: for example, see `Array.new/1` or `Enum.into/2`.
iex> Enum.into([1, 2, 3], Iteraptor.Array.new())
#Array<[1, 2, 3]>
`Array`s do implement `Access` behaviour.
iex> array = Iteraptor.Array.new([%{foo: 42}, %{bar: :baz}])
iex> get_in(array, [0, :foo])
42
"""
# Arrays have an underlying Map. Array elements are values of said map,
# indices are keys.
@type value :: term
@type t :: %__MODULE__{map: %{required(non_neg_integer) => any()}}
defstruct map: %{}, version: 1
@behaviour Access
alias Iteraptor.Array
@doc """
Returns a new array.
iex> Iteraptor.Array.new()
#Array<[]>
Creates an array of the given length or from enumerable. Might we used to wrap
the existing instance of `Iteraptor.Array`.
iex> Iteraptor.Array.new(3)
#Array<[nil, nil, nil]>
iex> Iteraptor.Array.new([:foo, :bar, 42])
#Array<[:foo, :bar, 42]>
Also the transformation function might be passed via second argument.
iex> Iteraptor.Array.new([1, 2, 3], fn x -> 2 * x end)
#Array<[2, 4, 6]>
"""
# @spec new(enumerable :: nil | t() | integer() | Enum.t(), transform :: (term() -> any())) :: t()
def new(enumerable \\ nil, transform \\ nil)
def new(nil, nil), do: %Array{}
def new(%__MODULE__{} = array, nil), do: array
def new(n, nil) when is_integer(n) and n >= 0,
do: Iteraptor.Array.new(List.duplicate(nil, n))
def new(enumerable, nil) do
list = Enum.to_list(enumerable)
map =
0..(length(list) - 1)
|> Enum.zip(list)
|> Enum.into(%{})
%Array{map: map}
end
def new(enumerable, transform) when is_function(transform, 1) do
list =
enumerable
|> Enum.map(&transform.(&1))
|> Enum.to_list()
map =
0..(length(list) - 1)
|> Enum.zip(list)
|> Enum.into(%{})
%Array{map: map}
end
@doc """
Appends another enumerable to the array.
iex> array = Iteraptor.Array.new([1, 2, 3])
iex> Iteraptor.Array.append(array, [4, 5])
#Array<[1, 2, 3, 4, 5]>
"""
@spec append(t(), any()) :: t()
def append(%Array{map: map} = array, other) do
index = map |> Map.keys() |> List.last() || -1
map =
if Enumerable.impl_for(other) do
appender =
other
|> Enum.reduce({index + 1, []}, fn e, {i, acc} -> {i + 1, [{i, e} | acc]} end)
|> elem(1)
|> Enum.reverse()
|> Enum.into(%{})
Map.merge(map, appender)
else
Map.put(map, index + 1, other)
end
%Array{array | map: map}
end
@doc """
Returns the `value` at `index` in `array`, or `default` if index is out of array bounds.
iex> array = Iteraptor.Array.new([42])
iex> Iteraptor.Array.get(array, 0)
42
iex> Iteraptor.Array.get(array, 2, 42)
42
"""
@spec get(t(), non_neg_integer(), any()) :: any()
def get(array, index, default \\ nil)
def get(%Array{map: map}, index, default) when index < 0 or index >= map_size(map), do: default
def get(%Array{map: map}, index, _default), do: map[index]
@doc """
Pops (deletes) `value` at `index` from `array`, setting the value at the
respective index to `nil`.
Returns a tuple containing the value removed and the new array.
iex> array = Iteraptor.Array.new([1, 2, 3])
iex> {elem, array} = Iteraptor.Array.pop(array, 1)
iex> elem
2
iex> array
#Array<[1, nil, 3]>
"""
@impl Access
@spec pop(t(), non_neg_integer()) :: {any(), t()}
def pop(%Array{map: map} = array, index) do
value = map[index]
{value, %{array | map: Map.put(map, index, nil)}}
end
@doc """
Sets the `value` at `index` in `array`, expanding the array if necessary.
Returns a new array.
iex> array = Iteraptor.Array.new([42])
iex> Iteraptor.Array.set(array, 0, :foo)
#Array<[:foo]>
iex> Iteraptor.Array.set(array, 2, :bar)
#Array<[42, nil, :bar]>
"""
@spec set(t(), non_neg_integer(), any()) :: t()
def set(%Array{map: map} = array, index, value) do
size = Array.size(array)
map =
if size > index do
Map.put(map, index, value)
else
fill = Enum.reverse([value | List.duplicate(nil, index - size)])
Map.merge(map, Enum.into(Enum.zip(size..index, fill), %{}))
end
%Array{array | map: map}
end
@doc """
Trims `nil` values from the tail of the `Array`. Returns a trimmed array.
iex> array = Iteraptor.Array.new([42, nil, nil])
#Array<[42, nil, nil]>
iex> Iteraptor.Array.trim(array)
#Array<[42]>
"""
@spec trim(array :: t()) :: t()
def trim(%Array{map: map} = array) do
map =
map
|> Enum.reverse()
|> Enum.drop_while(fn
{_, nil} -> true
_ -> false
end)
|> Enum.reverse()
|> Enum.into(%{})
%Array{array | map: map}
end
@doc """
Returns the number of elements in `array`.
iex> Iteraptor.Array.size(Iteraptor.Array.new([1, 2, 3]))
3
"""
@spec size(t()) :: non_neg_integer()
def size(%Array{map: map}), do: map_size(map)
@doc """
Converts `array` to a list.
iex> Iteraptor.Array.to_list(Iteraptor.Array.new([1, 2, 3]))
[1, 2, 3]
"""
@spec to_list(t()) :: [any()]
def to_list(%Array{map: map}),
do: map |> Enum.sort() |> Enum.map(&elem(&1, 1))
@doc """
Converts a tuple given as parameter to `array`.
iex> Iteraptor.Array.from_tuple({1, 2, 3})
#Array<[1, 2, 3]>
"""
@spec from_tuple(tuple :: tuple()) :: t()
def from_tuple(tuple) when is_tuple(tuple),
do: tuple |> Tuple.to_list() |> Array.new()
### Access behaviour
@doc false
@impl true
def fetch(%Array{map: map}, index), do: Map.fetch(map, index)
@doc false
@impl true
def get_and_update(%Array{map: map} = array, index, function) do
case function.(map[index]) do
:pop -> Array.pop(array, index)
{get_value, update_value} -> {get_value, Array.set(array, index, update_value)}
end
end
defimpl Enumerable do
def count(array) do
{:ok, Array.size(array)}
end
def member?(%Array{map: map}, val) do
{:ok,
!!Enum.find(map, fn
{_, ^val} -> true
{_, _} -> false
end)}
end
def reduce(array, acc, fun),
do: Enumerable.List.reduce(Array.to_list(array), acc, fun)
if Version.compare(System.version(), "1.10.0-dev") == :lt do
def slice(array) do
{:ok, Array.size(array), &Enumerable.List.slice(Array.to_list(array), &1, &2)}
end
else
defp slice(_list, _start, 0, _size), do: []
defp slice(list, start, count, size) when start + count == size, do: list |> drop(start)
defp slice(list, start, count, _size), do: list |> drop(start) |> take(count)
defp drop(list, 0), do: list
defp drop([_ | tail], count), do: drop(tail, count - 1)
defp take(_list, 0), do: []
defp take([head | tail], count), do: [head | take(tail, count - 1)]
def slice(array) do
size = Array.size(array)
{:ok, size, &slice(Array.to_list(array), &1, &2, size)}
end
end
end
defimpl Collectable do
def into(array) do
fun = fn
list, {:cont, x} -> [x | list]
list, :done -> Array.append(array, Enum.reverse(list))
_, :halt -> :ok
end
{[], fun}
end
end
defimpl Inspect do
import Inspect.Algebra
def inspect(array, opts) do
opts = %Inspect.Opts{opts | charlists: :as_lists}
concat(["#Array<", Inspect.List.inspect(Array.to_list(array), opts), ">"])
end
end
end