defmodule BiMap do
@moduledoc """
Bi-directional map implementation backed by two maps.
> In computer science, a bidirectional map, or hash bag, is an associative data
> structure in which the `(key, value)` pairs form a one-to-one correspondence.
> Thus the binary relation is functional in each direction: `value` can also
> act as a key to `key`. A pair `(a, b)` thus provides a unique coupling
> between a `a` and `b` so that `b` can be found when `a` is used as a key and
> `a` can be found when `b` is used as a key.
>
> ~[Wikipedia](https://en.wikipedia.org/wiki/Bidirectional_map)
Entries in bimap do not follow any order.
BiMaps do not impose any restriction on the key and value type: anything can be
a key in a bimap, and also anything can be a value. As a bidirectional
key-value structure, bimaps do not allow duplicated keys and values. This means
it is not possible to store `[(A, B), (A, C)]` or `[(X, Z), (Y, Z)]` in
the bimap. If you need to lift this restriction to only not allowing duplicated
key-value pairs, check out `BiMultiMap`.
Keys and values are compared using the exact-equality operator (`===`).
## Example
iex> bm = BiMap.new(a: 1, b: 2)
BiMap.new([a: 1, b: 2])
iex> BiMap.get(bm, :a)
1
iex> BiMap.get_key(bm, 2)
:b
iex> BiMap.put(bm, :a, 3)
BiMap.new([a: 3, b: 2])
iex> BiMap.put(bm, :c, 2)
BiMap.new([a: 1, c: 2])
## Protocols
`BiMap` implements `Enumerable`, `Collectable` and `Inspect` protocols.
"""
@typedoc "Key type"
@type k :: term
@typedoc "Value type"
@type v :: term
@typedoc false
@opaque internal_keys(k, v) :: %{optional(k) => v}
@typedoc false
@opaque internal_values(k, v) :: %{optional(v) => k}
@type t(k, v) :: %BiMap{
keys: internal_keys(k, v),
values: internal_values(k, v)
}
@type t :: t(term, term)
defstruct keys: %{}, values: %{}
@doc """
Creates a new bimap.
## Examples
iex> BiMap.new
BiMap.new([])
"""
@spec new :: t
def new, do: %BiMap{}
@doc """
Creates a bimap from `enumerable` of key-value pairs.
Duplicated pairs are removed; the latest one prevails.
## Examples
iex> BiMap.new([a: "foo", b: "bar"])
BiMap.new([a: "foo", b: "bar"])
"""
@spec new(Enum.t()) :: t
def new(enumerable)
def new(%BiMap{} = bimap), do: bimap
def new(enum) do
Enum.reduce(enum, new(), fn pair, bimap ->
BiMap.put(bimap, pair)
end)
end
@doc """
Creates a bimap from `enumerable` via transform function returning key-value
pairs.
## Examples
iex> BiMap.new([1, 2, 1], fn x -> {x, x * 2} end)
BiMap.new([{1, 2}, {2, 4}])
"""
@spec new(Enum.t(), (term -> {k, v})) :: t
def new(enumerable, transform)
def new(enum, f) do
Enum.reduce(enum, new(), fn term, bimap ->
BiMap.put(bimap, f.(term))
end)
end
@doc """
Returns the number of elements in `bimap`.
The size of a bimap is the number of key-value pairs that the map contains.
## Examples
iex> BiMap.size(BiMap.new)
0
iex> bimap = BiMap.new([a: "foo", b: "bar"])
iex> BiMap.size(bimap)
2
"""
@spec size(t) :: non_neg_integer
def size(bimap)
def size(%BiMap{keys: keys}) do
map_size(keys)
end
@doc """
Returns `key ➜ value` mapping of `bimap`.
## Examples
iex> bimap = BiMap.new([a: "foo", b: "bar"])
iex> BiMap.left(bimap)
%{a: "foo", b: "bar"}
"""
@spec left(t) :: %{k => v}
def left(bimap)
def left(%BiMap{keys: keys}), do: keys
@doc """
Returns `value ➜ key` mapping of `bimap`.
## Examples
iex> bimap = BiMap.new([a: "foo", b: "bar"])
iex> BiMap.right(bimap)
%{"foo" => :a, "bar" => :b}
"""
@spec right(t) :: %{v => k}
def right(bimap)
def right(%BiMap{values: values}), do: values
@doc """
Returns all keys from `bimap`.
## Examples
iex> bimap = BiMap.new([a: 1, b: 2])
iex> BiMap.keys(bimap)
[:a, :b]
"""
@spec keys(t) :: [k]
def keys(bimap)
def keys(%BiMap{keys: keys}), do: Map.keys(keys)
@doc """
Returns all values from `bimap`.
## Examples
iex> bimap = BiMap.new([a: 1, b: 2])
iex> BiMap.values(bimap)
[1, 2]
"""
@spec values(t) :: [v]
def values(bimap)
def values(%BiMap{values: values}), do: Map.keys(values)
@doc """
Checks if `bimap` contains `{key, value}` pair.
## Examples
iex> bimap = BiMap.new([a: "foo", b: "bar"])
iex> BiMap.member?(bimap, :a, "foo")
true
iex> BiMap.member?(bimap, :a, "bar")
false
"""
@spec member?(t, k, v) :: boolean
def member?(bimap, key, value)
def member?(%BiMap{keys: keys}, key, value) do
Map.has_key?(keys, key) and keys[key] === value
end
@doc """
Convenience shortcut for `member?/3`.
"""
@spec member?(t, {k, v}) :: boolean
def member?(bimap, kv)
def member?(bimap, {key, value}), do: member?(bimap, key, value)
@doc """
Checks if `bimap` contains `key`.
## Examples
iex> bimap = BiMap.new([a: "foo", b: "bar"])
iex> BiMap.has_key?(bimap, :a)
true
iex> BiMap.has_key?(bimap, :x)
false
"""
@spec has_key?(t, k) :: boolean
def has_key?(bimap, key)
def has_key?(%BiMap{keys: keys}, left) do
Map.has_key?(keys, left)
end
@doc """
Checks if `bimap` contains `value`.
## Examples
iex> bimap = BiMap.new([a: "foo", b: "bar"])
iex> BiMap.has_value?(bimap, "foo")
true
iex> BiMap.has_value?(bimap, "moo")
false
"""
@spec has_value?(t, v) :: boolean
def has_value?(bimap, value)
def has_value?(%BiMap{values: values}, value) do
Map.has_key?(values, value)
end
@doc """
Checks if two bimaps are equal.
Two bimaps are considered to be equal if they contain the same keys and those
keys contain the same values.
## Examples
iex> Map.equal?(BiMap.new([a: 1, b: 2]), BiMap.new([b: 2, a: 1]))
true
iex> Map.equal?(BiMap.new([a: 1, b: 2]), BiMap.new([b: 1, a: 2]))
false
"""
@spec equal?(t, t) :: boolean
def equal?(bimap1, bimap2)
def equal?(%BiMap{keys: keys1}, %BiMap{keys: keys2}) do
Map.equal?(keys1, keys2)
end
@doc """
Gets the value for specific `key` in `bimap`
If `key` is present in `bimap` with value `value`, then `value` is returned.
Otherwise, `default` is returned (which is `nil` unless specified otherwise).
## Examples
iex> BiMap.get(BiMap.new(), :a)
nil
iex> bimap = BiMap.new([a: 1])
iex> BiMap.get(bimap, :a)
1
iex> BiMap.get(bimap, :b)
nil
iex> BiMap.get(bimap, :b, 3)
3
"""
@spec get(t, k, v) :: v
def get(bimap, key, default \\ nil)
def get(%BiMap{keys: keys}, key, default) do
Map.get(keys, key, default)
end
@doc """
Gets the key for specific `value` in `bimap`
This function is exact mirror of `get/3`.
## Examples
iex> BiMap.get_key(BiMap.new, 1)
nil
iex> bimap = BiMap.new([a: 1])
iex> BiMap.get_key(bimap, 1)
:a
iex> BiMap.get_key(bimap, 2)
nil
iex> BiMap.get_key(bimap, 2, :b)
:b
"""
@spec get_key(t, v, k) :: k
def get_key(bimap, value, default \\ nil)
def get_key(%BiMap{values: values}, value, default) do
Map.get(values, value, default)
end
@doc """
Fetches the value for specific `key` in `bimap`
If `key` is present in `bimap` with value `value`, then `{:ok, value}` is
returned. Otherwise, `:error` is returned.
## Examples
iex> BiMap.fetch(BiMap.new(), :a)
:error
iex> bimap = BiMap.new([a: 1])
iex> BiMap.fetch(bimap, :a)
{:ok, 1}
iex> BiMap.fetch(bimap, :b)
:error
"""
@spec fetch(t, k) :: {:ok, v} | :error
def fetch(bimap, key)
def fetch(%BiMap{keys: keys}, key) do
Map.fetch(keys, key)
end
@doc """
Fetches the value for specific `key` in `bimap`.
Raises `ArgumentError` if the key is absent.
## Examples
iex> bimap = BiMap.new([a: 1])
iex> BiMap.fetch!(bimap, :a)
1
"""
@spec fetch!(t, k) :: v
def fetch!(bimap, key)
def fetch!(bimap, key) do
case fetch(bimap, key) do
{:ok, value} -> value
:error -> raise ArgumentError, "key #{inspect(key)} not found in: #{inspect(bimap)}"
end
end
@doc """
Fetches the key for specific `value` in `bimap`
This function is exact mirror of `fetch/2`.
## Examples
iex> BiMap.fetch_key(BiMap.new, 1)
:error
iex> bimap = BiMap.new([a: 1])
iex> BiMap.fetch_key(bimap, 1)
{:ok, :a}
iex> BiMap.fetch_key(bimap, 2)
:error
"""
@spec fetch_key(t, v) :: {:ok, k} | :error
def fetch_key(bimap, value)
def fetch_key(%BiMap{values: values}, value) do
Map.fetch(values, value)
end
@doc """
Fetches the key for specific `value` in `bimap`.
Raises `ArgumentError` if the value is absent. This function is exact mirror of `fetch!/2`.
## Examples
iex> bimap = BiMap.new([a: 1])
iex> BiMap.fetch_key!(bimap, 1)
:a
"""
@spec fetch_key!(t, v) :: k
def fetch_key!(bimap, value)
def fetch_key!(bimap, value) do
case fetch_key(bimap, value) do
{:ok, key} -> key
:error -> raise ArgumentError, "value #{inspect(value)} not found in: #{inspect(bimap)}"
end
end
@doc """
Inserts `{key, value}` pair into `bimap`.
If either `key` or `value` is already in `bimap`, any overlapping bindings are
deleted.
## Examples
iex> bimap = BiMap.new
BiMap.new([])
iex> bimap = BiMap.put(bimap, :a, 0)
BiMap.new([a: 0])
iex> bimap = BiMap.put(bimap, :a, 1)
BiMap.new([a: 1])
iex> BiMap.put(bimap, :b, 1)
BiMap.new([b: 1])
"""
@spec put(t, k, v) :: t
def put(%BiMap{} = bimap, key, value) do
%{keys: keys, values: values} = bimap |> BiMap.delete_key(key) |> BiMap.delete_value(value)
%{bimap | keys: Map.put(keys, key, value), values: Map.put(values, value, key)}
end
@doc """
Convenience shortcut for `put/3`
"""
@spec put(t, {k, v}) :: t
def put(bimap, kv)
def put(bimap, {key, value}), do: put(bimap, key, value)
@doc """
Inserts `{key, value}` pair into `bimap` if `key` is not already in `bimap`.
If `key` already exists in `bimap`, `bimap` is returned unchanged.
If `key` does not exist and `value` is already in `bimap`, any overlapping bindings are
deleted.
## Examples
iex> bimap = BiMap.new
BiMap.new([])
iex> bimap = BiMap.put_new_key(bimap, :a, 0)
BiMap.new([a: 0])
iex> bimap = BiMap.put_new_key(bimap, :a, 1)
BiMap.new([a: 0])
iex> BiMap.put_new_key(bimap, :b, 1)
BiMap.new([a: 0, b: 1])
iex> BiMap.put_new_key(bimap, :c, 1)
BiMap.new([a: 0, c: 1])
"""
@spec put_new_key(t, k, v) :: t
def put_new_key(%BiMap{} = bimap, key, value) do
if BiMap.has_key?(bimap, key) do
bimap
else
put(bimap, key, value)
end
end
@doc """
Inserts `{key, value}` pair into `bimap` if `value` is not already in `bimap`.
If `value` already exists in `bimap`, `bimap` is returned unchanged.
If `value` does not exist and `key` is already in `bimap`, any overlapping bindings are
deleted.
## Examples
iex> bimap = BiMap.new
BiMap.new([])
iex> bimap = BiMap.put_new_value(bimap, :a, 0)
BiMap.new([a: 0])
iex> bimap = BiMap.put_new_value(bimap, :a, 1)
BiMap.new([a: 1])
iex> BiMap.put_new_value(bimap, :b, 1)
BiMap.new([a: 1])
iex> BiMap.put_new_value(bimap, :c, 2)
BiMap.new([a: 1, c: 2])
"""
@spec put_new_value(t, k, v) :: t
def put_new_value(%BiMap{} = bimap, key, value) do
if BiMap.has_value?(bimap, value) do
bimap
else
put(bimap, key, value)
end
end
@doc """
Deletes `{key, value}` pair from `bimap`.
If the `key` does not exist, or `value` does not match, returns `bimap`
unchanged.
## Examples
iex> bimap = BiMap.new([a: 1, b: 2])
iex> BiMap.delete(bimap, :b, 2)
BiMap.new([a: 1])
iex> BiMap.delete(bimap, :c, 3)
BiMap.new([a: 1, b: 2])
iex> BiMap.delete(bimap, :b, 3)
BiMap.new([a: 1, b: 2])
"""
@spec delete(t, k, v) :: t
def delete(%BiMap{keys: keys, values: values} = bimap, key, value) do
case Map.fetch(keys, key) do
{:ok, ^value} ->
%{bimap | keys: Map.delete(keys, key), values: Map.delete(values, value)}
_ ->
bimap
end
end
@doc """
Deletes `{key, _}` pair from `bimap`.
If the `key` does not exist, returns `bimap` unchanged.
## Examples
iex> bimap = BiMap.new([a: 1, b: 2])
iex> BiMap.delete_key(bimap, :b)
BiMap.new([a: 1])
iex> BiMap.delete_key(bimap, :c)
BiMap.new([a: 1, b: 2])
"""
@spec delete_key(t, k) :: t
def delete_key(%BiMap{keys: keys, values: values} = bimap, key) do
case Map.fetch(keys, key) do
{:ok, value} ->
%{bimap | keys: Map.delete(keys, key), values: Map.delete(values, value)}
:error ->
bimap
end
end
@doc """
Deletes `{_, value}` pair from `bimap`.
If the `value` does not exist, returns `bimap` unchanged.
## Examples
iex> bimap = BiMap.new([a: 1, b: 2])
iex> BiMap.delete_value(bimap, 2)
BiMap.new([a: 1])
iex> BiMap.delete_value(bimap, 3)
BiMap.new([a: 1, b: 2])
"""
@spec delete_value(t, v) :: t
def delete_value(%BiMap{keys: keys, values: values} = bimap, value) do
case Map.fetch(values, value) do
{:ok, key} ->
%{bimap | keys: Map.delete(keys, key), values: Map.delete(values, value)}
:error ->
bimap
end
end
@doc """
Convenience shortcut for `delete/3`.
"""
@spec delete(t, {k, v}) :: t
def delete(bimap, kv)
def delete(bimap, {key, value}), do: delete(bimap, key, value)
@doc """
Returns list of unique key-value pairs in `bimap`.
## Examples
iex> bimap = BiMap.new([a: "foo", b: "bar"])
iex> BiMap.to_list(bimap)
[a: "foo", b: "bar"]
"""
@spec to_list(t) :: [{k, v}]
def to_list(bimap)
def to_list(%BiMap{keys: keys}) do
Map.to_list(keys)
end
defimpl Enumerable do
def reduce(bimap, acc, fun) do
Enumerable.List.reduce(BiMap.to_list(bimap), acc, fun)
end
def member?(bimap, val) do
{:ok, BiMap.member?(bimap, val)}
end
def count(bimap) do
{:ok, BiMap.size(bimap)}
end
def slice(_bimap) do
{:error, __MODULE__}
end
end
defimpl Collectable do
def into(original) do
{original,
fn
bimap, {:cont, pair} -> BiMap.put(bimap, pair)
bimap, :done -> bimap
_, :halt -> :ok
end}
end
end
defimpl Inspect do
import Inspect.Algebra
def inspect(bimap, opts) do
concat(["BiMap.new(", to_doc(BiMap.to_list(bimap), opts), ")"])
end
end
end