One9.Ms (omultiset v0.3.0)

Operations on simple non-struct multiplicity maps ("multisets", t/1).

"Lax" multisets (t_lax/1) may include some entries for absent elements, and are, by default, handled correctly by all methods in this module.

iex> One9.Ms.counts([1, 2, 1, 3]) # like Python's "collections.Counter(...)"
%{1 => 2, 2 => 1, 3 => 1} # 1 occurs twice, 2 occurs once, and 3 occurs once.

iex> One9.Ms.to_list(%{1 => 2, 2 => 1, 3 => 1}) |> Enum.sort()
[1, 1, 2, 3] # the original list is got back, order notwithstanding

iex> One9.Ms.counts(nil: 5) # WARNING: GENERALLY NOT WHAT YOU WANT...
...> |> One9.Ms.to_list()
[{nil, 5}] # the tuple {nil, 5} occurs once.
iex> One9.Ms.from_counts(nil: 5) # ...USE THIS INSTEAD
...> |> One9.Ms.to_list()
[nil, nil, nil, nil, nil] # the atom nil occurs ten times.

iex> One9.Ms.member?(%{"damn" => 0}, "damn")
false # "damn" occurs zero times, so it is considered not to be a member

Summary

Functions

A more efficient alternative to Enum.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 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.

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) 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 the support of a multiset.

Convert a multiset into a complete List of elements (including repeats).

Return the union of two multisets.

Types

t0()

@type t0() :: t0(term())

t0(value)

@type t0(value) :: Enumerable.t({value, non_neg_integer()})

t()

@type t() :: t(term())

t(value)

@type t(value) :: %{optional(value) => pos_integer()}

t_lax()

@type t_lax() :: t_lax(term())

t_lax(value)

@type t_lax(value) :: %{optional(value) => non_neg_integer()}

Functions

at(ms, index)

@spec at(t0(e), non_neg_integer()) :: e when e: term()

A more efficient alternative to Enum.to_list(ms) |> Enum.at(index).

iex> %{41 => 100, 42 => 10**100, 43 => 100} # an extremely large multiset
...> |> One9.Ms.at(10**99) # pull some element out of the middle of it
42

count_element(ms, element)

@spec count_element(t_lax(), term()) :: non_neg_integer()

Determine the multiplicity of an element in a multiset.

iex> ms = One9.Ms.counts(["duck", "duck", "goose", "duck"])
%{"duck" => 3, "goose" => 1}
iex> ms |> One9.Ms.count_element("duck")
3
iex> ms |> One9.Ms.count_element("buffalo")
0

See also size/1.

counts(elements \\ [])

@spec counts(Enumerable.t(e)) :: t(e) when e: term()

Create a well-formed multiset from any (finite) Enumerable of values.

See also from_counts/1.

delete(ms, element, count \\ :all)

@spec delete(t(e), term(), non_neg_integer() | :all) :: t(e) when e: term()
@spec delete(t_lax(e), term(), non_neg_integer() | :all) :: t_lax(e) when e: term()

Delete copies of an element from a multiset.

Returns a well-formed multiset whenever input is well-formed.

difference(ms1, ms2)

@spec difference(t(e), t_lax()) :: t(e) when e: term()
@spec difference(t_lax(e), t_lax()) :: t_lax(e) when e: term()

Return the first multiset, less the elements in the second.

Calculations are done soft aka "clamping".

Returns a well-formed multiset whenever the first argument is well-formed.

iex> %{"dog" => 1, "cat" => 10}
...> |> One9.Ms.difference(%{"dog" => 3, "cat" => 3})
%{"cat" => 7}

See also difference!/2.

difference!(ms1, ms2)

@spec difference!(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.

Raises KeyError if the second element is not a subset of the first.

Returns a well-formed multiset whenever the first argument is well-formed.

iex> %{"dog" => 1, "cat" => 10}
...> |> One9.Ms.difference!(%{"dog" => 3, "cat" => 3})
** (KeyError) key {"dog", 3} not found in: %{"cat" => 10, "dog" => 1}

See also difference/2.

empty?(ms, strict \\ :lax)

@spec empty?(t_lax(), :lax) :: boolean()
@spec empty?(t(), :strict) :: boolean()

Determine whether the multiset is empty.

Examples

iex> One9.Ms.empty?(%{})
true
iex> One9.Ms.empty?(%{"pony" => 1})
false

iex> One9.Ms.empty?(%{"lie" => 0})
true

See also size/1.

equals?(ms1, ms2, strict \\ :lax)

@spec equals?(t_lax(), t_lax(), :lax) :: boolean()
@spec equals?(t(), t(), :strict) :: boolean()

Determine whether two multisets are equal.

Examples

iex> One9.Ms.equals?(%{"dog" => 999}, %{"cat" => 1})
false

iex> One9.Ms.equals?(%{"dollars" => 0}, %{"cents" => 0})
true

See also size/1.

from_counts(counts \\ %{})

@spec from_counts(counts :: t0(e)) :: t(e) when e: term()

Construct a well-formed multiset from any enumerable of multiplicities (t0/0).

Duplicate entries are combined additively.

Examples

iex> One9.Ms.from_counts(true: 3, false: 99, true: 1)
%{true: 4, false: 99}

See also counts/1.

intersection(ms1, ms2)

@spec intersection(t_lax(e | e1), t_lax(e | e2)) :: t(e)
when e: term(), e1: term(), e2: term()

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}

member?(ms, element, strict \\ :lax)

@spec member?(t(), term(), :strict) :: boolean()
@spec member?(t_lax(), term(), :lax) :: boolean()

Determine whether a value is present at all in a multiset.

Examples

iex> ms = %{"bug" => 0, "cheese" => 1}
iex> ms |> One9.Ms.member?("cheese")
true
iex> ms |> One9.Ms.member?("bug")
false
iex> ms |> One9.Ms.member?("horse")
false

See also count_element/2.

put(ms, element, count \\ 1)

@spec put(t(e1), e2, pos_integer()) :: t(e1 | e2) when e1: term(), e2: term()
@spec put(t_lax(e1), e2, pos_integer()) :: t_lax(e1 | e2) when e1: term(), e2: term()

Add additional copies (by default) of the element into the multiset.

Return value is well-formed whenever the input is.

Examples

iex>

size(ms)

@spec size(t_lax()) :: non_neg_integer()

Determine the cardinality of a multiset.

iex> One9.Ms.counts(["duck", "duck", "goose", "duck"])
...> |> One9.Ms.size()
4

See also count_element/2 and empty/1.

subset?(ms1, ms2)

@spec subset?(t_lax(), t_lax()) :: boolean()

Determine whether the first multiset is a (non-strict) subset of the second.

Either/both operands may be non-well-formed.

iex> One9.Ms.subset?(%{a: 1, b: 2}, %{a: 1, b: 3, c: 5})
true

iex> One9.Ms.subset?(%{a: 1, b: 2}, %{a: 1, b: 2})
true

iex> One9.Ms.subset?(%{a: 1, b: 2}, %{a: 1, b: 1})
false
iex> One9.Ms.subset?(%{a: 1, b: 2}, %{a: 1})
false

sum(ms1, ms2)

@spec sum(t(e1), t(e2)) :: t(e1 | e2) when e1: term(), e2: term()
@spec sum(t_lax(e1), t_lax(e2)) :: t_lax(e1 | e2) when e1: term(), e2: term()

support(ms, strict \\ :lax)

@spec support(t(e), :strict) :: [e] when e: term()
@spec support(t_lax(e), :lax) :: [e] when e: term()

Convert a multiset into a List of unique elements.

iex> One9.Ms.counts([1, 1, 2, 42, 42, 42, 42, 42])
...> |> One9.Ms.support()
...> |> Enum.sort()
[1, 2, 42]

support_count(ms, strict \\ :lax)

@spec support_count(t_lax(), :lax) :: non_neg_integer()
@spec support_count(t(), :strict) :: non_neg_integer()

Return the cardinality of the support of a multiset.

See also size/1, to get the cardinality of the multiset itself.

Examples

iex> One9.Ms.support_count(%{a: 1, b: 99})
2

iex> One9.Ms.support_count(%{a: 99, b: 0})
1

iex> One9.Ms.support_count(%{a: 99, b: 0}, :strict) # INCORRECT
2 # Only pass :strict when you can **guarantee** the multiset is well-formed!

symmetric_difference(ms1, ms2)

@spec symmetric_difference(t(e1), t(e2)) :: t(e1 | e2) when e1: term(), e2: term()

Examples

iex> One9.Ms.symmetric_difference(
...>   %{"dog" => 10, "frog" => 1, "rat" => 100},
...>   %{"cat" => 20, "frog" => 1, "rat" => 67}
...> )
%{"dog" => 10, "cat" => 20, "rat" => 33}

take(ms, element, count)

@spec take(t(e), e1, non_neg_integer()) :: {t(e), [e1]} when e: term(), e1: term()
iex> One9.Ms.take(%{a: 1, b: 2}, :b, 1)
{%{a: 1, b: 1}, [:b]}

iex> One9.Ms.take(%{a: 1, b: 2}, :b, 3)
{%{a: 1}, [:b, :b]}

iex> One9.Ms.take(%{a: 1, b: 2}, :z, 999)
{%{a: 1, b: 2}, []}

to_list(ms)

@spec to_list(t0(e)) :: [e] when e: 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]

union(ms1, ms2)

@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}