Packages

Elixir wrapper around :ordsets

Current section

Files

Jump to
ord_set lib ord_set.ex
Raw

lib/ord_set.ex

defmodule OrdSet do
@moduledoc """
Elixir wrapper around `:ordsets` erlang module.
Acts like a normal `MapSet` but adds in sorted order.
What this library does is forward the methods to the `:ordsets` module but adds some Elixir flavour.
Like having the first argument be the set instead of the last,
have different function names to look like `MapSet`
OrdSet is just a list, so guards like `in/2`, `==/2` just work[^â„¢].
```elixir
iex> 4 in OrdSet.new([1,2])
false
iex> 1 in OrdSet.new([1,2])
true
iex> OrdSet.new([1,2,1,2]) == OrdSet.new([1,2])
true
iex> OrdSet.new([1,2,1,2]) == OrdSet.new([3])
false
iex> [1,2,1,2,1,1] |> OrdSet.new() |> length()
2
```
Also Enum functions just work[^â„¢].
```elixir
iex> [2,5,4,5,2,1] |> OrdSet.new() |> Enum.sort()
OrdSet.new([1,2,4,5])
```
[^â„¢]: You should ofcourse test this yourself
"""
@compile {:inline, put: 2, delete: 2, member?: 2, equal?: 2, filter: 2, reject: 2}
@type t(term) :: list(term)
@type t :: list
@doc """
```elixir
iex> init = fn
...> set when OrdSet.is_empty(set) -> OrdSet.new([1])
...> set -> set
...> end
iex> OrdSet.new() |> init.()
OrdSet.new([1])
iex> [3,6] |> OrdSet.new() |> init.()
OrdSet.new([3,6])
```
"""
@spec is_empty(t()) :: boolean
defguard is_empty(set) when set == []
@doc """
Creates a empty `OrdSet`
"""
@spec new() :: OrdSet.t()
defdelegate new(), to: :ordsets
@doc """
Creates a new `OrdSet` from `Enumerable.t()`
```elixir
iex> OrdSet.new(0..5)
OrdSet.new([0,1,2,3,4,5])
iex> OrdSet.new(5..0//-1)
OrdSet.new([0,1,2,3,4,5])
```
"""
@spec new(Enumerable.t()) :: OrdSet.t()
def new(list) when is_list(list), do: :ordsets.from_list(list)
def new(enumerable) do
enumerable
|> Enum.to_list()
|> new()
end
@doc """
Creates a new `OrdSet` from `Enumerable.t()` with given function
```elixir
iex> OrdSet.new([0,1,2,3,4,5], &rem(&1, 3))
OrdSet.new([0,1,2])
```
"""
@spec new(Enumerable.t(), (term -> term)) :: OrdSet.t()
def new(enumerable, func) do
enumerable
|> Enum.map(func)
|> new()
end
@doc """
Puts the item to the end of the set
```elixir
iex> [1,2,3,4] |> OrdSet.new() |> OrdSet.put(5)
OrdSet.new([1,2,3,4,5])
iex> [1,2,3,4] |> OrdSet.new() |> OrdSet.put(3)
OrdSet.new([1,2,3,4])
iex> [1,2,4] |> OrdSet.new() |> OrdSet.put(3)
OrdSet.new([1,2,3,4])
```
"""
@spec put(t(), term) :: t()
def put(set, item) do
:ordsets.add_element(item, set)
end
@doc """
Deletes the item from the set
```elixir
iex> [1,2,4,3] |> OrdSet.new() |> OrdSet.delete(2)
OrdSet.new([1,4,3])
```
"""
@spec delete(t(), term) :: t()
def delete(set, item) do
:ordsets.del_element(item, set)
end
@spec size(t()) :: pos_integer()
defdelegate size(set), to: :ordsets
@doc """
Returns a set containing only members that set_a and set_b have in common.
```elixir
iex> OrdSet.intersection(OrdSet.new(1..4), OrdSet.new(3..6))
[3, 4]
```
"""
@spec intersection(t(), t()) :: t()
defdelegate intersection(set_a, set_b), to: :ordsets
@doc """
Returns a set that is set_a without the members of set_b
```elixir
iex> OrdSet.difference(OrdSet.new(1..4), OrdSet.new(3..6))
[1, 2]
```
"""
@spec difference(t(), t()) :: t()
defdelegate difference(set_a, set_b), to: :ordsets, as: :subtract
@doc """
Returns a set containing all members of set_a and set_b.
```elixir
iex> OrdSet.union(OrdSet.new(1..4), OrdSet.new(3..6))
[1,2,3,4,5,6]
iex> OrdSet.new(1..2) |> OrdSet.union(OrdSet.new(5..6)) |> OrdSet.union(OrdSet.new(1..3))
[1,2,3,5,6]
```
"""
@spec union(t(), t()) :: t()
defdelegate union(set_a, set_b), to: :ordsets
@doc """
iex> 2 in OrdSet.new(1..3)
true
iex> 1..3 |> OrdSet.new() |> OrdSet.member?(2)
true
iex> 1..3 |> OrdSet.new() |> OrdSet.member?(8)
false
"""
@spec member?(t(), term) :: boolean
def member?(set, item) do
:ordsets.is_element(item, set)
end
@doc """
iex> OrdSet.equal?(OrdSet.new(1..3), OrdSet.new(1..3))
true
iex> OrdSet.equal?(OrdSet.new(1..4), OrdSet.new(1..3))
false
iex> OrdSet.equal?(OrdSet.new([1,2,3]), OrdSet.new([3,2,1]))
true
"""
@spec member?(t(), t()) :: boolean
def equal?(set_a, set_b) do
set_a == set_b
end
@doc """
Checks if set_a's members are all contained in set_b.
```elixir
iex> OrdSet.disjoint?(OrdSet.new([1, 2, 3]), OrdSet.new([4, 5, 6]))
true
iex> OrdSet.disjoint?(OrdSet.new([4, 5, 6]), OrdSet.new([2, 3, 4]))
false
```
"""
@spec disjoint?(t(), t()) :: boolean
defdelegate disjoint?(set_a, set_b), to: :ordsets, as: :is_disjoint
@doc """
Checks if set_a's members are all contained in set_b.
```elixir
iex> MapSet.subset?(MapSet.new([4]), MapSet.new([4, 5, 6]))
true
iex> OrdSet.subset?(OrdSet.new([4]), OrdSet.new([4, 5, 6]))
true
iex> MapSet.subset?(MapSet.new([4, 5, 6]), MapSet.new([4]))
false
iex> OrdSet.subset?(OrdSet.new([4, 5, 6]), OrdSet.new([4]))
false
```
"""
@spec subset?(t(), t()) :: boolean
defdelegate subset?(set_a, set_b), to: :ordsets, as: :is_subset
@doc """
Converts `OrdSet` to a list.
Ps: I don't think this does anything because a `OrdSet` is already a list
"""
@spec to_list(t()) :: list
defdelegate to_list(set), to: :ordsets
@doc """
Filters the set by returning only the elements from `set` for which invoking `fun` returns a truthy value.
```elixir
iex> MapSet.filter(MapSet.new(1..5), fn x -> x > 3 end)
MapSet.new([4, 5])
```
"""
@spec filter(t(), (term -> as_boolean(term))) :: t()
def filter(set, func) do
:ordsets.filter(func, set)
end
@doc """
Returns a set by excluding the elements from `set` for which invoking `fun` returns a truthy value.
```
iex> OrdSet.reject(OrdSet.new(1..5), fn x -> rem(x, 2) != 0 end)
OrdSet.new([2, 4])
```
"""
@spec reject(t(), (term -> as_boolean(term))) :: t()
def reject(set, func) do
:ordsets.filter(&(!func.(&1)), set)
end
@doc """
Get the largest element in the set
```elixir
iex> 0..5 |> OrdSet.new() |> OrdSet.max()
5
```
"""
@spec max(t()) :: term
defdelegate max(set), to: List, as: :last
@doc """
Get the smallest element in the set
```elixir
iex> 0..5 |> OrdSet.new() |> OrdSet.min()
0
```
"""
@spec min(t()) :: term
defdelegate min(set), to: List, as: :first
end