Packages

A wrapper around Erlang's set module

Current section

Files

Jump to
s2 src s2.erl
Raw

src/s2.erl

-module(s2).
-moduledoc """
Here lies s2, a wrapper around the Erlang's [`sets`](https://www.erlang.org/doc/apps/stdlib/sets.html)
module which gives you the `{version, 2}` representation without having to worry about it. This library
exists because I was using [elp](https://github.com/WhatsApp/erlang-language-platform) during
Advent of Code and it kept complaining about using `{version, 2}` for `sets:new/1` and `sets:from_list/1`.
The documentation says,
> This new representation will become the default in future Erlang/OTP versions.
but it hasn't happened yet so either this library will be useful or it will push Erlang/OTP
to make it the default. Either way, he API surface is more similar to Haskell's
[`Data.Set`](https://hackage.haskell.org/package/containers-0.7/docs/Data-Set.html) module
because of my own personal preferences. For reference, the `{version, 2}` representation is just
a map where all the values are `[]`.
If you have any suggestions or find an issue please submit an issue on [github](https://github.com/chiroptical/s2)
""".
-export([
insert/2,
delete/2,
filter/2,
fold/3,
from_list/1,
singleton/1,
intersections/1,
intersection/2,
is_empty/1,
is_disjoint/2,
is_element/2,
is_subset/2,
is_proper_subset/2,
to_list/1,
map/2,
empty/0,
size/1,
difference/2,
unions/1,
union/2,
partition/2,
cartesian_product/2
]).
-export_type([set/1]).
% The memory documentation https://www.erlang.org/doc/system/memory.html
% suggests this is 1 word of storage for a zero element list.
-define(VALUE, []).
-doc "As returned by `empty/0`.".
-opaque set(Element) :: #{Element => ?VALUE}.
-doc """
Return the empty set.
```erlang
1> s2:empty().
#{}
```
""".
-spec empty() -> set(_).
empty() ->
sets:new([{version, 2}]).
-doc """
Construct a set with a single element.
```erlang
1> s2:singleton(1).
#{1 => []}
```
""".
-spec singleton(Elem) -> Set when Elem :: Element, Set :: set(Element).
singleton(X) ->
s2:from_list([X]).
-doc """
Construct a set from a list of elements.
```erlang
1> s2:from_list([1, 2, 3]).
#{1 => [], 2 => [], 3 => []}
```
""".
-spec from_list(List) -> Set when List :: [Element], Set :: set(Element).
from_list(List) ->
sets:from_list(List, [{version, 2}]).
-doc """
Add an element to a set.
```erlang
1> s2:insert(1, s2:singleton(2)).
#{1 => [],2 => []}
```
""".
-spec insert(Element, Set1) -> Set2 when Set1 :: set(Element), Set2 :: set(Element).
insert(Elem, Set1) ->
sets:add_element(Elem, Set1).
-doc """
Remove an element from the set. It won't fail if the element doesn't exist.
```erlang
1> Set = s2:from_list([1, 2, 3]).
#{1 => [],2 => [],3 => []}
2> s2:delete(1, Set).
#{2 => [],3 => []}
3> s2:delete(4, Set).
#{1 => [],2 => [],3 => []}
```
""".
-spec delete(Element, Set1) -> Set2 when Set1 :: set(Element), Set2 :: set(Element).
delete(Elem, Set) ->
sets:del_element(Elem, Set).
-doc """
Keep elements in the set for which the predicate returns `true`.
```erlang
1> S = s2:from_list([1, 2, 3, 4]).
#{1 => [],2 => [],3 => [],4 => []}
2> s2:filter(fun (X) -> X >= 2 end, S).
#{2 => [], 3 => [], 4 => []}
```
""".
-spec filter(Pred, Set1) -> Set2 when
Pred :: fun((Element) -> boolean()), Set1 :: set(Element), Set2 :: set(Element).
filter(Pred, Set1) ->
sets:filter(Pred, Set1).
-doc """
Given a binary operation on elements of the set and an accumulator,
an initial accumulator, and a set collect the result of applying
the binary operation to each element of the set starting from
the initial accumulator.
```erlang
1> S = s2:from_list([1, 2, 3, 4]).
#{1 => [],2 => [],3 => [],4 => []}
2> s2:fold(fun (Elem, Acc) -> Elem + Acc end, 0, S).
10
```
""".
-spec fold(Function, Acc0, Set) -> Acc1 when
Function :: fun((Element, AccIn) -> AccOut),
Set :: set(Element),
Acc0 :: Acc,
Acc1 :: Acc,
AccIn :: Acc,
AccOut :: Acc.
fold(Function, Acc0, Set) ->
sets:fold(Function, Acc0, Set).
-doc """
Given a list of sets, take the set intersection of all sets.
```erlang
1> s2:intersections([s2:singleton(1), s2:singleton(1)]).
#{1 => []}
2> s2:intersections([s2:singleton(1), s2:singleton(2)]).
#{}
```
""".
-spec intersections(SetList) -> Set when SetList :: [set(Element), ...], Set :: set(Element).
intersections(SetList) ->
sets:intersection(SetList).
-doc """
Given two sets, take the set intersection of them.
```erlang
1> s2:intersection(s2:singleton(1), s2:singleton(1)).
#{1 => []}
2> s2:intersection(s2:singleton(1), s2:singleton(2)).
#{}
```
""".
-spec intersection(Set1, Set2) -> Set3 when
Set1 :: set(Element), Set2 :: set(Element), Set3 :: set(Element).
intersection(Set1, Set2) ->
sets:intersection(Set1, Set2).
-doc """
Given two sets, determine if their intersection is the empty set.
Returns `true` if the intersection is the empty set and `false` otherwise.
```erlang
1> s2:is_disjoint(s2:singleton(1), s2:singleton(2)).
true
2> s2:is_disjoint(s2:from_list([1, 2]), s2:singleton(2)).
false
```
""".
-spec is_disjoint(Set1, Set2) -> boolean() when Set1 :: set(Element), Set2 :: set(Element).
is_disjoint(Set1, Set2) ->
sets:is_disjoint(Set1, Set2).
-doc """
Given an element and a set, if the element is contained in the set return
`true` otherwise return `false`.
```erlang
1> s2:is_element(1, s2:singleton(1)).
true
2> s2:is_element(1, s2:singleton(2)).
false
```
""".
-spec is_element(Element, Set) -> boolean() when Set :: set(Element).
is_element(Element, Set) ->
sets:is_element(Element, Set).
-doc """
Given a set, if it is the empty set return `true` else return `false`.
```erlang
1> s2:is_empty(s2:empty()).
true
2> s2:is_empty(s2:singleton(1)).
false
```
""".
-spec is_empty(Set) -> boolean() when Set :: set(_).
is_empty(Set) ->
sets:is_empty(Set).
-doc """
Given two sets, if every element of the first is contained in the second
return `true` otherwise return `false`.
```erlang
1> s2:is_subset(s2:singleton(1), s2:singleton(1)).
true
2> s2:is_subset(s2:from_list([1, 2]), s2:from_list([1, 2])).
true
3> s2:is_subset(s2:from_list([1, 2]), s2:from_list([1, 2, 3])).
true
```
""".
-spec is_subset(Set1, Set2) -> boolean() when Set1 :: set(Element), Set2 :: set(Element).
is_subset(Set1, Set2) ->
sets:is_subset(Set1, Set2).
-doc """
Given two sets, if every element of the first is contained in the second
**and** the sets are different return `true` otherwise return `false`.
```erlang
1> s2:is_proper_subset(s2:singleton(1), s2:singleton(1)).
true
2> s2:is_proper_subset(s2:from_list([1, 2]), s2:from_list([1, 2])).
false
3> s2:is_proper_subset(s2:from_list([1, 2]), s2:from_list([1, 2, 3])).
true
```
""".
-spec is_proper_subset(Set1, Set2) -> boolean() when Set1 :: set(Element), Set2 :: set(Element).
is_proper_subset(Set1, Set2) ->
sets:is_subset(Set1, Set2) and (sets:size(Set1) =/= sets:size(Set2)).
-doc """
Given a unary function and a set, apply the unary function to each element
of the set to form a new set. The input and output set may not be the same
size.
```erlang
1> s2:map(fun (X) -> X + 1 end, s2:from_list([1, 2, 3])).
#{2 => [],3 => [],4 => []}
2> s2:map(fun (_X) -> 1 end, s2:from_list([1, 2, 3])).
#{1 => []}
```
""".
-spec map(Fun, Set1) -> Set2 when
Fun :: fun((Element1) -> Element2), Set1 :: set(Element1), Set2 :: set(Element2).
map(Fun, Set1) ->
sets:map(Fun, Set1).
-doc """
Given a set, return the number of elements in the set.
```erlang
1> s2:size(s2:empty()).
0
2> s2:size(s2:from_list([1, 2])).
2
```
""".
-spec size(Set) -> non_neg_integer() when Set :: set(_).
size(Set) ->
sets:size(Set).
-doc """
Given two sets, return a new set with elements of the second
removed from the first.
```erlang
1> s2:difference(s2:from_list([1, 2, 3]), s2:empty()).
#{1 => [],2 => [],3 => []}
2> s2:difference(s2:from_list([1, 2, 3]), s2:from_list([1])).
#{2 => [],3 => []}
```
""".
-spec difference(Set1, Set2) -> Set3 when
Set1 :: set(Element), Set2 :: set(Element), Set3 :: set(Element).
difference(Set1, Set2) ->
sets:subtract(Set1, Set2).
-doc """
Given a list of sets, return a new set with every element from
every set.
```erlang
1> s2:unions([s2:singleton(1), s2:singleton(2), s2:singleton(3)]).
#{1 => [],2 => [],3 => []}
```
""".
-spec unions(SetList) -> Set when SetList :: [set(Element)], Set :: set(Element).
unions(SetList) ->
sets:union(SetList).
-doc """
Given two sets, return a new set with every element from both sets.
```erlang
1> s2:union(s2:singleton(1), s2:singleton(2)).
#{1 => [],2 => []}
```
""".
-spec union(Set1, Set2) -> Set3 when
Set1 :: set(Element), Set2 :: set(Element), Set3 :: set(Element).
union(Set1, Set2) ->
sets:union(Set1, Set2).
-doc """
Given a set, return a list with all elements from the set.
```erlang
1> s2:to_list(s2:empty()).
[]
2> s2:to_list(s2:singleton(1)).
[1]
3> s2:to_list(s2:from_list([1, 2, 3])).
[1,2,3]
```
""".
-spec to_list(Set) -> List when Set :: set(Element), List :: [Element].
to_list(Set) ->
sets:to_list(Set).
-doc """
partition(Pred, Set) -> {Keep, Discard}
Similar to `filter/2` but it returns a `Tuple` of two elements. The first
contains elements where the predicate evaluates to `true`. The second contains
elements where the predicate evaluates to `false`.
""".
-spec partition(Pred, Set1) -> {Set2, Set2} when
Pred :: fun((Element) -> boolean()), Set1 :: set(Element), Set2 :: set(Element).
partition(Pred, Set1) ->
sets:fold(
fun(Elem, {Keep, Discard}) ->
case Pred(Elem) of
true -> {s2:insert(Elem, Keep), Discard};
false -> {Keep, s2:insert(Elem, Discard)}
end
end,
{s2:empty(), s2:empty()},
Set1
).
-doc """
Given two sets, pair every element in the first set with every element in the second.
```erlang
1> Product = s2:cartesian_product(s2:from_list([1, 2]), s2:from_list([3, 4])).
#{{1,3} => [],{1,4} => [],{2,3} => [],{2,4} => []}
2> Product =:= s2:from_list([{1, 3}, {1, 4}, {2, 3}, {2, 4}]).
true
```
""".
-spec cartesian_product(Set1, Set2) -> Set3 when
Set1 :: set(Element1), Set2 :: set(Element2), Set3 :: set({Element1, Element2}).
cartesian_product(Set1, Set2) ->
s2:fold(
fun(X, Outer) ->
s2:fold(
fun(Y, Inner) ->
s2:insert({X, Y}, Inner)
end,
Outer,
Set2
)
end,
s2:empty(),
Set1
).