NaturalSet v0.3.1 NaturalSet View Source
Functions that work on sets of small integers >= 0.
NaturalSet
is an alternative set type in Elixir
emulating the MapSet
interface as closely as possible,
with one important limitation: every element must be a non-negative integers.
Many of the NaturalSet
doctests and unit tests were adapted from MapSet
.
NaturalSet
illustrates the construction of a functional data structure from scratch,
with support for streaming and the Inspect
, Enumerable
, and Collectable
protocols.
By definition, sets contain unique elements. Trying to insert a duplicate is a no-op:
iex> natural_set = NaturalSet.new()
#NaturalSet<[]>
iex> natural_set = NaturalSet.put(natural_set, 3)
#NaturalSet<[3]>
iex> natural_set |> NaturalSet.put(2) |> NaturalSet.put(2)
#NaturalSet<[2, 3]>
NaturalSet.new/1
accepts an enumerable of elements:
iex> NaturalSet.new(1..5)
#NaturalSet<[1, 2, 3, 4, 5]>
An NaturalSet
is represented internally using the %NaturalSet{}
struct.
This struct can be used whenever there's a need to pattern match on something being an NaturalSet
:
iex> match?(%NaturalSet{}, NaturalSet.new())
true
The %NaturalSet{}
struct contains a single field—bits
—
an integer which is used as a bit vector where each bit set to 1
represents
a number present in the set.
An empty set is stored as bits = 0
:
iex> empty = NaturalSet.new()
iex> empty.bits
0
iex> NaturalSet.member?(empty, 0)
false
A set containing just a 0
is stored as bits = 1
,
because the bit at 0
is set, so the element 0
is present:
iex> set_with_zero = NaturalSet.new([0])
iex> set_with_zero.bits
1
iex> NaturalSet.member?(set_with_zero, 0)
true
A set with a 2
is stored as bits = 4
,
because the bit at 2
is set, so the element 2
is present:
iex> set_with_two = NaturalSet.new([2])
iex> set_with_two.bits
4
iex> NaturalSet.member?(set_with_two, 2)
true
A set with the elements 0
and 1
is stored as bits = 3
,
because 3
is 0b11
, so the bits at 0
and 1
are set:
iex> set_with_zero_and_one = NaturalSet.new([0, 1])
#NaturalSet<[0, 1]>
iex> set_with_zero_and_one.bits
3
NaturalSet
s can also be constructed starting from other collection-type data
structures: for example, see NaturalSet.new/1
or Enum.into/2
.
All the content of an NaturalSet
is represented by a single integer,
which in Elixir is limited only by available memory.
Using an integer as a bit vector allows set operations like union and intersection
to be implemented using fast bitwise operators.
See the source code of NaturalSet.union
and NaturalSet.intersection
.
A bit vector is efficient only for storing sets of small integers, or high-density sets where a large percentage of the possible elements are present. The memory usage is proportional only to the largest element stored, not to the number of elements present. If the largest element in a set is 1_000_000, the raw bits will take 125_000 bytes (⅛), regardless of the number of elements in the set.
This package was inspired by the intset
example from chapter 6 of
The Go Programming Language, by Alan. A. A. Donovan and Brian W. Kernighan.
Link to this section Summary
Functions
Returns a new set which is a copy of natural_set
without element
.
Returns a new set like natural_set1
without the members of natural_set2
.
Checks if natural_set1
and natural_set2
have no members in common.
Checks if two sets are equal.
Returns a set containing only members that natural_set1
and natural_set2
have in common.
Returns the number of elements in natural_set
.
The corresponding function in MapSet
is size
.
This function is named length
because it traverses the natural_set
,
so it runs in O(n) time.
Checks if natural_set
contains element
.
Returns a new empty NaturalSet
.
Creates a set from an enumerable.
Creates a set from an enumerable via the transformation function.
Inserts element
into natural_set
if natural_set
doesn't already contain it.
Returns a stream function yielding the elements of natural_set
one by one in ascending order.
The stream lazily traverses the bits of the natural_set
as needed.
Checks if natural_set1
's members are all contained in natural_set2
.
Returns a list containing all members of natural_set
in ascending order.
Returns a set containing all members of natural_set1
and natural_set2
.
Link to this section Functions
Returns a new set which is a copy of natural_set
without element
.
Examples
iex> natural_set = NaturalSet.new([1, 2, 3])
iex> NaturalSet.delete(natural_set, 4)
#NaturalSet<[1, 2, 3]>
iex> NaturalSet.delete(natural_set, 2)
#NaturalSet<[1, 3]>
Returns a new set like natural_set1
without the members of natural_set2
.
Examples
iex> NaturalSet.difference(NaturalSet.new([1, 2]), NaturalSet.new([2, 3, 4]))
#NaturalSet<[1]>
Checks if natural_set1
and natural_set2
have no members in common.
Examples
iex> NaturalSet.disjoint?(NaturalSet.new([1, 2]), NaturalSet.new([3, 4]))
true
iex> NaturalSet.disjoint?(NaturalSet.new([1, 2]), NaturalSet.new([2, 3]))
false
Checks if two sets are equal.
Examples
iex> NaturalSet.equal?(NaturalSet.new([1, 2]), NaturalSet.new([2, 1, 1]))
true
iex> NaturalSet.equal?(NaturalSet.new([1, 2]), NaturalSet.new([3, 4]))
false
Returns a set containing only members that natural_set1
and natural_set2
have in common.
Examples
iex> NaturalSet.intersection(NaturalSet.new([1, 2]), NaturalSet.new([2, 3, 4]))
#NaturalSet<[2]>
iex> NaturalSet.intersection(NaturalSet.new([1, 2]), NaturalSet.new([3, 4]))
#NaturalSet<[]>
Returns the number of elements in natural_set
.
The corresponding function in MapSet
is size
.
This function is named length
because it traverses the natural_set
,
so it runs in O(n) time.
Example
iex> NaturalSet.length(NaturalSet.new([10, 20, 30]))
3
Checks if natural_set
contains element
.
Examples
iex> NaturalSet.member?(NaturalSet.new([1, 2, 3]), 2)
true
iex> NaturalSet.member?(NaturalSet.new([1, 2, 3]), 4)
false
Returns a new empty NaturalSet
.
Example
iex> NaturalSet.new()
#NaturalSet<[]>
Creates a set from an enumerable.
Examples
iex> NaturalSet.new([10, 5, 7])
#NaturalSet<[5, 7, 10]>
iex> NaturalSet.new(3..7)
#NaturalSet<[3, 4, 5, 6, 7]>
iex> NaturalSet.new([3, 3, 3, 2, 2, 1])
#NaturalSet<[1, 2, 3]>
Creates a set from an enumerable via the transformation function.
Examples
iex> NaturalSet.new([1, 3, 1], fn x -> 2 * x end)
#NaturalSet<[2, 6]>
Inserts element
into natural_set
if natural_set
doesn't already contain it.
Examples
iex> NaturalSet.put(NaturalSet.new([1, 2, 3]), 3)
#NaturalSet<[1, 2, 3]>
iex> NaturalSet.put(NaturalSet.new([1, 2, 3]), 4)
#NaturalSet<[1, 2, 3, 4]>
Returns a stream function yielding the elements of natural_set
one by one in ascending order.
The stream lazily traverses the bits of the natural_set
as needed.
Examples
iex> my_stream = NaturalSet.new([10, 5, 7]) |> NaturalSet.stream
iex> my_stream |> is_function
true
iex> my_stream |> Stream.map(&(&1 * 10)) |> Enum.to_list
[50, 70, 100]
Checks if natural_set1
's members are all contained in natural_set2
.
This function checks if natural_set1
is a subset of natural_set2
.
Examples
iex> NaturalSet.subset?(NaturalSet.new([1, 2]), NaturalSet.new([1, 2, 3]))
true
iex> NaturalSet.subset?(NaturalSet.new([1, 2, 3]), NaturalSet.new([1, 2]))
false
Returns a list containing all members of natural_set
in ascending order.
Examples
iex> NaturalSet.new([2, 3, 1]) |> NaturalSet.to_list
[1, 2, 3]
Returns a set containing all members of natural_set1
and natural_set2
.
Examples
iex> NaturalSet.union(NaturalSet.new([1, 2]), NaturalSet.new([2, 3, 4]))
#NaturalSet<[1, 2, 3, 4]>