One9.Ms (omultiset v0.4.0-rc.0)
Operations on simple non-struct multiplicity maps (aka "multisets", t/1
and t_lax/1
).
Most operations have 3 modes:
One9.Ms._func(%{...}, ...)
: "Just works", suitable for most use-cases. Returns correct results regardless of whether arguments aret/1
ort_lax/1
, but guarantees to returnt/1
whenever all arguments aret/1
.One9.Ms._func(%{...}, ..., :strict)
: "Fast path" when invariants can be controlled, such as inside a struct. Guarantees to returnt/1
, but invokes UB when any arguments are nott/1
.One9.Ms._func(%{...}, ..., :lax)
: Guarantees to preserve all keys from the inputs, even at the cost of having0
-valued entries in the result. (Testing TODO, but I believe these methods will also be faster than the "just works" methods.)
Summary
Functions
A more efficient alternative to One9.Ms.to_list(ms) |> Enum.at(index)
.
Determine the multiplicity of an element in a multiset.
Create a well-formed multiset from any (finite) Enumerable of values.
Delete (up to) the given number of copies of an element from a multiset.
Return the first multiset, less the elements in the second.
Return the first multiset, less the elements in the second, which must be a subset.
Determine whether the multiset is empty.
Determine whether two multisets are equal.
Construct a well-formed multiset from any enumerable of multiplicities (t0/0
).
Return the intersection of two multisets.
Determine whether a value is present at all in a multiset.
Add additional copies (by default, 1 copy) of the element into the multiset.
Determine the cardinality of a multiset.
Determine whether the first multiset is a (non-strict) subset of the second.
Convert a multiset into a List of unique elements.
Return the cardinality of a multiset's support.
Convert a multiset into a complete List of elements (including repeats).
Return the union of two multisets.
Types
@type t0(value) :: Enumerable.t({value, non_neg_integer()})
@type t(value) :: %{optional(value) => pos_integer()}
@type t_lax(value) :: %{optional(value) => non_neg_integer()}
Functions
@spec at(t(e) | t_lax(e) | t0(e), non_neg_integer()) :: e when e: term()
A more efficient alternative to One9.Ms.to_list(ms) |> Enum.at(index)
.
See also to_list/1
.
@spec count_element(t() | t_lax(), term()) :: non_neg_integer()
Determine the multiplicity of an element in a multiset.
Examples
iex> %{"cat" => 10, "dog" => 10}
...> |> One9.Ms.count_element("dog")
10
iex> %{"cat" => 10, "dog" => 10}
...> |> One9.Ms.count_element("unicorn")
0
See also size/1
.
@spec counts([e]) :: t(e) when e: term()
@spec counts(Enumerable.t(e)) :: t(e) when e: term()
Create a well-formed multiset from any (finite) Enumerable of values.
Examples
iex> One9.Ms.counts()
%{}
iex> One9.Ms.counts([1, 2, 1, 1, 1])
%{1 => 4, 2 => 1} # four (4) copies of the number 1, and one (1) copy of the number 2.
iex> One9.Ms.counts(%{1 => 4}) # WATCH OUT: This is probably not what you want!
%{{1, 4} => 1} # one (1) copy of the tuple {1, 4}.
See also from_counts/1
.
@spec delete(t(e), term()) :: t(e) when e: term()
@spec delete(t_lax(e), term()) :: t_lax(e) when e: term()
Delete (up to) the given number of copies of an element from a multiset.
Examples
iex> %{"cat" => 10, "dog" => 10} |> delete("cat", 1)
%{"dog" => 10, "cat" => 9}
iex> %{"cat" => 10, "dog" => 10} |> delete("cat", 10)
%{"dog" => 10}
iex> %{"cat" => 10, "dog" => 10} |> delete("cat", 999)
%{"dog" => 10}
iex> %{"cat" => 10, "dog" => 10} |> delete("cat", 999, :lax)
%{"cat" => 0, "dog" => 10}
iex> %{"cat" => 10, "dog" => 10} |> delete("unicorn", 1)
%{"cat" => 10, "dog" => 10}
iex> %{"cat" => 10, "dog" => 10} |> delete("unicorn", 1, :lax)
%{"cat" => 10, "dog" => 10}
See also difference/2
.
@spec delete(t(e), term(), non_neg_integer() | :all, :strict) :: t(e) when e: term()
@spec delete(t_lax(e), term(), non_neg_integer() | :all, :lax) :: t_lax(e) when e: term()
@spec difference(t(e), t() | t_lax(), :strict) :: t(e) when e: term()
@spec difference(t_lax(e), t_lax(), :lax) :: t_lax(e) when e: term()
Return the first multiset, less the elements in the second.
Calculations are done soft aka "clamping".
Examples
iex> %{"cat" => 10, "dog" => 10}
...> |> difference(%{"dog" => 3})
%{"cat" => 10, "dog" => 7}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference(%{"dog" => 3, "cat" => 10})
%{"dog" => 7}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference(%{"dog" => 3, "cat" => 999})
%{"dog" => 7}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference(%{"dog" => 3, "cat" => 10}, :lax)
%{"cat" => 0, "dog" => 7}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference(%{"dog" => 3, "cat" => 999}, :lax)
%{"cat" => 0, "dog" => 7}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference(%{"dog" => 3, "unicorn" => 1})
%{"cat" => 10, "dog" => 7}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference(%{"dog" => 3, "unicorn" => 1}, :lax)
%{"cat" => 10, "dog" => 7}
See also delete/3
, difference!/2
.
@spec difference!(t(e), t(e) | t_lax(e)) :: t(e) when e: term()
@spec difference!(t_lax(e), t_lax(e)) :: t_lax(e) when e: term()
Return the first multiset, less the elements in the second, which must be a subset.
Raises KeyError
if the second multiset is not a subset of the first.
You should generally not use this function; instead, prefer testing subset?/2
and
then using difference/2
, which (I believe; TODO: test) is slightly faster.
Examples
iex> %{"cat" => 10, "dog" => 10}
...> |> difference!(%{"dog" => 3})
%{"cat" => 10, "dog" => 7}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference!(%{"dog" => 3, "cat" => 10})
%{"dog" => 7}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference!(%{"dog" => 3, "cat" => 999})
** (KeyError) key {"cat", 999} not found in: %{"cat" => 10, "dog" => 10}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference!(%{"dog" => 3, "cat" => 10}, :lax)
%{"dog" => 7, "cat" => 0}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference!(%{"dog" => 3, "cat" => 999}, :lax)
** (KeyError) key {"cat", 999} not found in: %{"cat" => 10, "dog" => 10}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference!(%{"dog" => 3, "unicorn" => 1})
** (KeyError) key {"unicorn", 1} not found in: %{"cat" => 10, "dog" => 10}
iex> %{"cat" => 10, "dog" => 10}
...> |> difference!(%{"dog" => 3, "unicorn" => 0}, :lax)
%{"dog" => 7, "cat" => 10}
iex> %{"cat" => 10, "dog" => 10} # WATCH OUT: lax mode doesn't change the semantics!
...> |> difference!(%{"dog" => 3, "unicorn" => 1}, :lax)
** (KeyError) key {"unicorn", 1} not found in: %{"cat" => 10, "dog" => 10}
See also difference/2
.
Determine whether the multiset is empty.
Examples
iex> One9.Ms.empty?(%{})
true
iex> One9.Ms.empty?(%{"pony" => 1})
false
iex> One9.Ms.empty?(%{"unicorn" => 0})
true
See also size/1
, well_formed?/1
.
Determine whether two multisets are equal.
Examples
iex> One9.Ms.equals?(
...> %{"cat" => 10, "dog" => 10},
...> %{"dog" => 10, "cat" => 10}
...> )
true
iex> One9.Ms.equals?(
...> %{"cat" => 10, "dog" => 10},
...> %{"cat" => 10, "dog" => 10, "pony" => 1}
...> )
false
iex> One9.Ms.equals?(
...> %{"cat" => 10, "dog" => 10},
...> %{"cat" => 10, "dog" => 10, "unicorn" => 0}
...> )
true
Construct a well-formed multiset from any enumerable of multiplicities (t0/0
).
Duplicate entries are combined additively.
Examples
iex> One9.Ms.from_counts(%{"cat" => 10, "dog" => 10, "unicorn" => 0})
%{"cat" => 10, "dog" => 10}
iex> One9.Ms.from_counts(true: 99, nil: 0, true: 1, false: 1)
%{true: 100, false: 1}
See also counts/1
.
Return the intersection of two multisets.
Either/both operands may be non-well-formed. Result will always be well-formed.
iex> One9.Ms.intersection(%{a: 1, b: 2, c: 3}, %{b: 1, c: 2})
%{b: 1, c: 2}
iex> One9.Ms.intersection(%{a: 1, b: 999, z: 999}, %{a: 3, b: 2, c: 1})
%{a: 1, b: 2}
Determine whether a value is present at all in a multiset.
Examples
iex> %{"cat" => 10, "dog" => 10, "unicorn" => 0}
...> |> One9.Ms.member?("cat")
true
iex> %{"cat" => 10, "dog" => 10, "unicorn" => 0}
...> |> One9.Ms.member?("unicorn")
false
See also count_element/2
.
@spec put(t(e1), e2) :: t(e1 | e2) when e1: term(), e2: term()
@spec put(t_lax(e1), e2) :: t_lax(e1 | e2) when e1: term(), e2: term()
Add additional copies (by default, 1 copy) of the element into the multiset.
Examples
iex> %{"cat" => 10, "dog" => 10}
...> |> One9.Ms.put("pony")
%{"cat" => 10, "dog" => 10, "pony" => 1}
iex> %{"cat" => 10, "dog" => 10}
...> |> One9.Ms.put("unicorn", 0)
%{"cat" => 10, "dog" => 10}
iex> %{"cat" => 10, "dog" => 10}
...> |> One9.Ms.put("unicorn", 0, :lax)
%{"cat" => 10, "dog" => 10, "unicorn" => 0}
@spec size(t() | t_lax()) :: non_neg_integer()
Determine the cardinality of a multiset.
Examples
iex> %{"cat" => 10, "dog" => 10} |> One9.Ms.size()
20
See also support_size/1
, count_element/2
and empty?/1
.
Determine whether the first multiset is a (non-strict) subset of the second.
Examples
iex> One9.Ms.subset?(
...> %{"cat" => 10, "dog" => 9},
...> %{"cat" => 10, "dog" => 10}
...> )
true
iex> One9.Ms.subset?(
...> %{"cat" => 10, "dog" => 10},
...> %{"cat" => 10, "dog" => 10}
...> )
true
iex> One9.Ms.subset?(
...> %{"cat" => 10, "unicorn" => 0},
...> %{"cat" => 10, "dog" => 10}
...> )
true
iex> One9.Ms.subset?(
...> %{"cat" => 10, "pony" => 1},
...> %{"cat" => 10, "dog" => 10}
...> )
false
Convert a multiset into a List of unique elements.
Examples
iex> One9.Ms.counts([1, 1, 2, 42, 42, 42, 42, 42])
...> |> One9.Ms.support()
...> |> Enum.sort()
[1, 2, 42]
See also to_list/1
, support_size/1
, size/1
.
@spec support_size(t() | t_lax()) :: non_neg_integer()
Return the cardinality of a multiset's support.
Examples
iex> %{"dog" => 10, "cat" => 10}
...> |> One9.Ms.support_size()
2
iex> %{"dog" => 10, "cat" => 10, "pony" => 1}
...> |> One9.Ms.support_size()
3
iex> %{"dog" => 10, "cat" => 10, "unicorn" => 0}
...> |> One9.Ms.support_size()
2
@spec support_size(t(), :strict) :: non_neg_integer()
@spec symmetric_difference(t(e1), t(e2)) :: t(e1 | e2) when e1: term(), e2: term()
@spec symmetric_difference(t_lax(e1), t_lax(e2)) :: t_lax(e1 | e2) when e1: term(), e2: term()
Examples
iex> One9.Ms.symmetric_difference(
...> %{"cat" => 10, "dog" => 10},
...> %{"cat" => 10, "dog" => 10}
...> )
%{}
iex> One9.Ms.symmetric_difference(
...> %{"cat" => 10, "dog" => 4},
...> %{"cat" => 3, "dog" => 10}
...> )
%{"cat" => 7, "dog" => 6}
iex> One9.Ms.symmetric_difference(
...> %{"cat" => 10, "dog" => 10, "pony" => 1},
...> %{"cat" => 10, "dog" => 15}
...> )
%{"dog" => 5, "pony" => 1}
iex> One9.Ms.symmetric_difference(
...> %{"cat" => 10, "dog" => 10, "unicorn" => 0},
...> %{"cat" => 10, "dog" => 10}, :lax
...> )
%{"cat" => 0, "dog" => 0, "unicorn" => 0}
@spec take(t(e1), e2, non_neg_integer()) :: {t(e1), [e2]} when e1: term(), e2: term()
@spec take(t_lax(e1), e2, non_neg_integer()) :: {t_lax(e1), [e2]} when e1: term(), e2: term()
Examples
iex> %{"cat" => 10, "dog" => 10}
...> |> One9.Ms.take("cat", 2)
{%{"cat" => 8, "dog" => 10}, ["cat", "cat"]}
iex> %{"cat" => 5, "dog" => 5}
...> |> One9.Ms.take("cat", 10000000000)
{%{"dog" => 5}, ["cat", "cat", "cat", "cat", "cat"]}
iex> %{"cat" => 5, "dog" => 5}
...> |> One9.Ms.take("cat", :all)
{%{"dog" => 5}, ["cat", "cat", "cat", "cat", "cat"]}
iex> %{"cat" => 5, "dog" => 5}
...> |> One9.Ms.take("cat", 10000000000, :lax)
{%{"cat" => 0, "dog" => 5}, ["cat", "cat", "cat", "cat", "cat"]}
@spec take(t(e1), e2, non_neg_integer(), :strict) :: {t(e1), [e2]} when e1: term(), e2: term()
@spec take(t_lax(e1), e2, non_neg_integer(), :lax) :: {t_lax(e1), [e2]} when e1: term(), e2: term()
Convert a multiset into a complete List of elements (including repeats).
Examples
iex> One9.Ms.to_list(%{a: 1, b: 2})
...> |> Enum.sort()
[:a, :b, :b]
iex> One9.Ms.to_list([a: 1, b: 2, a: 3])
...> |> Enum.sort()
[:a, :a, :a, :a, :b, :b]
@spec union(t(e1), t(e2)) :: t(e1 | e2) when e1: term(), e2: term()
@spec union(t_lax(e1), t_lax(e2)) :: t_lax(e1 | e2) when e1: term(), e2: term()
Return the union of two multisets.
Either/both operands may be non-well-formed. Result will be well-formed whenever both operands are well-formed.
iex> One9.Ms.union(%{a: 1, b: 2}, %{b: 1, c: 3})
%{a: 1, b: 2, c: 3}