Current section

Files

Jump to
aja lib rb_map.ex
Raw

lib/rb_map.ex

defmodule A.RBMap do
@moduledoc false
@behaviour Access
# TODO: inline what is relevant
# WARNING: be careful with non-tail recursive functions looping on the full tree!
@compile {:inline,
fetch: 2, fetch!: 2, put: 3, has_key?: 2, equal?: 2, equal_loop: 2, pop_existing: 2}
@type key :: term
@type value :: term
@opaque t(key, value) :: %__MODULE__{
root: A.RBTree.Map.tree(key, value),
size: non_neg_integer
}
@opaque t :: t(key, value)
@opaque iterator(key, value) :: A.RBTree.Map.iterator(key, value)
defstruct root: A.RBTree.Map.empty(), size: 0
@spec size(t) :: non_neg_integer
def size(rb_map)
def size(%__MODULE__{size: size}), do: size
@spec keys(t(k, value)) :: [k] when k: key
def keys(rb_map)
def keys(%__MODULE__{root: root}) do
A.RBTree.Map.foldr(root, [], fn key, _value, acc -> [key | acc] end)
end
@spec values(t(key, v)) :: [v] when v: value
def values(rb_map)
def values(%__MODULE__{root: root}) do
A.RBTree.Map.foldr(root, [], fn _key, value, acc -> [value | acc] end)
end
@spec to_list(t(k, v)) :: [{k, v}] when k: key, v: value
def to_list(%__MODULE__{root: root}), do: A.RBTree.Map.to_list(root)
@deprecated "Module A.RBMap will be removed"
@spec new() :: t
def new, do: %__MODULE__{}
@deprecated "Module A.RBMap will be removed"
@spec new(Enumerable.t()) :: t
def new(enumerable) do
{size, root} = A.RBTree.Map.empty() |> A.RBTree.Map.insert_many(enumerable)
%__MODULE__{root: root, size: size}
end
@spec new(Enumerable.t(), (term -> {k, v})) :: t(k, v) when k: key, v: value
def new(enumerable, transform) do
enumerable
|> Enum.map(transform)
|> new()
end
@spec has_key?(t(k, value), k) :: boolean when k: key
def has_key?(rb_map, key) do
case fetch(rb_map, key) do
{:ok, _} -> true
:error -> false
end
end
@impl Access
@spec fetch(t(k, v), k) :: {:ok, v} | :error when k: key, v: value
def fetch(rb_map, key)
def fetch(%__MODULE__{root: root}, key), do: A.RBTree.Map.fetch(root, key)
@spec fetch!(t(k, v), k) :: v when k: key, v: value
def fetch!(%__MODULE__{} = rb_map, key) do
case fetch(rb_map, key) do
{:ok, value} ->
value
_ ->
raise KeyError, key: key, term: rb_map
end
end
@spec put_new(t(k, v), k, v) :: t(k, v) when k: key, v: value
def put_new(%__MODULE__{} = rb_map, key, value) do
if has_key?(rb_map, key) do
rb_map
else
put(rb_map, key, value)
end
end
@spec replace(t(k, v), k, v) :: t(k, v) when k: key, v: value
def replace(%__MODULE__{} = rb_map, key, value) do
if has_key?(rb_map, key) do
put(rb_map, key, value)
else
rb_map
end
end
@spec replace!(t(k, v), k, v) :: t(k, v) when k: key, v: value
def replace!(%__MODULE__{} = rb_map, key, value) do
if has_key?(rb_map, key) do
put(rb_map, key, value)
else
raise KeyError, key: key, term: rb_map
end
end
@spec put_new_lazy(t(k, v), k, (() -> v)) :: t(k, v) when k: key, v: value
def put_new_lazy(%__MODULE__{} = rb_map, key, fun) when is_function(fun, 0) do
if has_key?(rb_map, key) do
rb_map
else
put(rb_map, key, fun.())
end
end
@spec get(t(k, v), [k]) :: t(k, v) when k: key, v: value
def take(rb_map, keys)
def take(%__MODULE__{root: root}, keys) when is_list(keys) do
keys
|> List.foldl([], fn key, acc ->
case A.RBTree.Map.fetch(root, key) do
{:ok, value} ->
[{key, value} | acc]
:error ->
acc
end
end)
|> new()
end
@spec get(t(k, v), k, v) :: v | nil when k: key, v: value
def get(%__MODULE__{} = rb_map, key, default \\ nil) do
case fetch(rb_map, key) do
{:ok, value} ->
value
:error ->
default
end
end
@spec get_lazy(t(k, v), k, v) :: v | nil when k: key, v: value
def get_lazy(%__MODULE__{} = rb_map, key, fun) when is_function(fun, 0) do
case fetch(rb_map, key) do
{:ok, value} ->
value
:error ->
fun.()
end
end
@spec put(t(k, v), k, v) :: v when k: key, v: value
def put(rb_map, key, value)
def put(%__MODULE__{root: root, size: size}, key, value) do
{result, new_root} = A.RBTree.Map.insert(root, key, value)
case result do
:new -> %__MODULE__{root: new_root, size: size + 1}
:overwrite -> %__MODULE__{root: new_root, size: size}
end
end
@spec delete(t(k, v), k) :: t(k, v) when k: key, v: value
def delete(%__MODULE__{} = rb_map, key) do
case pop_existing(rb_map, key) do
{_value, new_rb_map} -> new_rb_map
:error -> rb_map
end
end
@spec merge(t(k, v), t(k, v)) :: t(k, v) when k: key, v: value
def merge(%__MODULE__{} = rb_map1, %__MODULE__{} = rb_map2) do
# TODO optimize
A.RBTree.Map.foldl(rb_map2.root, rb_map1, fn key, value, acc -> put(acc, key, value) end)
end
@spec update(t(k, v), k, v, (v -> v)) :: t(k, v) when k: key, v: value
def update(%__MODULE__{} = rb_map, key, default, fun) when is_function(fun, 1) do
case fetch(rb_map, key) do
{:ok, value} ->
put(rb_map, key, fun.(value))
:error ->
put(rb_map, key, default)
end
end
@impl Access
@spec pop(t(k, v), k, v) :: {v, t(k, v)} when k: key, v: value
def pop(%__MODULE__{} = rb_map, key, default \\ nil) do
case pop_existing(rb_map, key) do
{value, new_rb_map} -> {value, new_rb_map}
:error -> {default, rb_map}
end
end
@spec pop!(t(k, v), k) :: {v, t(k, v)} when k: key, v: value
def pop!(%__MODULE__{} = rb_map, key) do
case pop_existing(rb_map, key) do
{value, new_rb_map} -> {value, new_rb_map}
:error -> raise KeyError, key: key, term: rb_map
end
end
@spec pop_lazy(t(k, v), k, (() -> v)) :: {v, t(k, v)} when k: key, v: value
def pop_lazy(%__MODULE__{} = rb_map, key, fun) when is_function(fun, 0) do
case pop_existing(rb_map, key) do
{value, new_rb_map} -> {value, new_rb_map}
:error -> {fun.(), rb_map}
end
end
@spec drop(t(k, v), [k]) :: t(k, v) when k: key, v: value
def drop(%__MODULE__{} = rb_map, keys) when is_list(keys) do
List.foldl(keys, rb_map, fn key, acc ->
delete(acc, key)
end)
end
@spec update!(t(k, v), k, v) :: t(k, v) when k: key, v: value
def update!(%__MODULE__{} = rb_map, key, fun) when is_function(fun, 1) do
case fetch(rb_map, key) do
{:ok, value} ->
put(rb_map, key, fun.(value))
:error ->
raise KeyError, key: key, term: rb_map
end
end
@impl Access
@spec get_and_update(t(k, v), k, (v -> {returned, v} | :pop)) :: {returned, t(k, v)}
when k: key, v: value, returned: term
def get_and_update(%__MODULE__{} = rb_map, key, fun) when is_function(fun, 1) do
A.Helpers.CustomMaps.get_and_update(rb_map, key, fun)
end
@spec get_and_update!(t(k, v), k, (v -> {returned, v} | :pop)) :: {returned, t(k, v)}
when k: key, v: value, returned: term
def get_and_update!(%__MODULE__{} = rb_map, key, fun) when is_function(fun, 1) do
A.Helpers.CustomMaps.get_and_update!(rb_map, key, fun)
end
@spec from_struct(atom | struct) :: t
def from_struct(struct) do
struct |> Map.from_struct() |> new()
end
@spec equal?(t, t) :: boolean
def equal?(%A.RBMap{} = rb_map1, %A.RBMap{} = rb_map2) do
rb_map1.size == rb_map2.size &&
equal_loop(A.RBTree.Map.iterator(rb_map1.root), A.RBTree.Map.iterator(rb_map2.root))
end
defp equal_loop(iterator1, iterator2) do
case {A.RBTree.Map.next(iterator1), A.RBTree.Map.next(iterator2)} do
{nil, nil} ->
true
{{key1, same_value, next_iter1}, {key2, same_value, next_iter2}} when key1 == key2 ->
equal_loop(next_iter1, next_iter2)
_ ->
false
end
end
@spec first(t(k, v), default) :: {k, v} | default when k: key, v: value, default: term
def first(rb_map, default \\ nil)
def first(%__MODULE__{root: root}, default) do
A.RBTree.Map.min(root) || default
end
@spec last(t(k, v), default) :: {k, v} | default when k: key, v: value, default: term
def last(rb_map, default \\ nil)
def last(%__MODULE__{root: root}, default) do
A.RBTree.Map.max(root) || default
end
@spec pop_first(t(k, v)) :: {k, v, t(k, v)} | nil when k: key, v: value
def pop_first(rb_map)
def pop_first(%__MODULE__{size: size, root: root}) do
case A.RBTree.Map.pop_min(root) do
{key, value, new_root} ->
new_rb_map = %__MODULE__{root: new_root, size: size - 1}
{key, value, new_rb_map}
:error ->
nil
end
end
@spec pop_last(t(k, v)) :: {k, v, t(k, v)} | nil when k: key, v: value
def pop_last(rb_map)
def pop_last(%__MODULE__{size: size, root: root}) do
case A.RBTree.Map.pop_max(root) do
{key, value, new_root} ->
new_rb_map = %__MODULE__{root: new_root, size: size - 1}
{key, value, new_rb_map}
:error ->
nil
end
end
def foldl(rb_map, acc, fun)
def foldl(%__MODULE__{root: root}, acc, fun) when is_function(fun, 3) do
A.RBTree.Map.foldl(root, acc, fun)
end
def foldr(rb_map, acc, fun)
def foldr(%__MODULE__{root: root}, acc, fun) when is_function(fun, 3) do
A.RBTree.Map.foldr(root, acc, fun)
end
# Iterators
# TODO document or doc false?
@doc false
@spec iterator(t(k, v)) :: iterator(k, v) when k: key, v: value
def iterator(%__MODULE__{root: root}), do: A.RBTree.Map.iterator(root)
@doc false
@spec next(iterator(k, v)) :: {k, v, iterator(k, v)} | nil
when k: key, v: value
defdelegate next(iterator), to: A.RBTree.Map
# Private functions
defp pop_existing(%{root: root, size: size}, key) do
case A.RBTree.Map.pop(root, key) do
{value, new_root} -> {value, %__MODULE__{root: new_root, size: size - 1}}
:error -> :error
end
end
# Not private, but only exposed for protocols
@doc false
def reduce(%__MODULE__{root: root}, acc, fun), do: A.RBTree.Map.reduce(root, acc, fun)
defimpl Enumerable do
def count(rb_map) do
{:ok, A.RBMap.size(rb_map)}
end
def member?(rb_map, key_value) do
with {key, value} <- key_value,
{:ok, ^value} <- A.RBMap.fetch(rb_map, key) do
{:ok, true}
else
_ -> {:ok, false}
end
end
def slice(_rb_map), do: {:error, __MODULE__}
defdelegate reduce(rb_map, acc, fun), to: A.RBMap
end
defimpl Collectable do
def into(rb_map) do
fun = fn
map_acc, {:cont, {key, value}} ->
A.RBMap.put(map_acc, key, value)
map_acc, :done ->
map_acc
_map_acc, :halt ->
:ok
end
{rb_map, fun}
end
end
defimpl Inspect do
import A.Helpers.CustomMaps, only: [implement_inspect: 3]
implement_inspect(A.RBMap, "#A.RBMap<", ">")
end
if Code.ensure_loaded?(Jason.Encoder) do
defimpl Jason.Encoder do
def encode(map, opts) do
map |> A.RBMap.to_list() |> Jason.Encode.keyword(opts)
end
end
end
end