Packages
inplace
0.6.3
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Mutable data structures
Current section
Files
Jump to
Current section
Files
lib/adt/bitset.ex
defmodule InPlace.BitSet do
@moduledoc """
BitSet is close in functionality to MapSet with integer values a s members.
The main difference is that BitSet has lower and upper bounds
for set values that have to be defined at the time of creation (see new/2).
"""
alias InPlace.Array
import Bitwise
import InPlace.BitUtils
def new(lower_bound, upper_bound)
when is_integer(lower_bound) and is_integer(upper_bound) and
lower_bound <= upper_bound do
{:bit_vector, atomics} = bit_vector = :bit_vector.new(upper_bound - lower_bound + 1)
%{
lower_bound: lower_bound,
upper_bound: upper_bound,
bit_vector: bit_vector,
offset: -lower_bound,
last_index: :atomics.info(atomics).size,
size: Array.new(1, 0),
minmax:
Array.new(2, 0)
|> tap(fn arr ->
Array.put(arr, 1, Array.inf())
Array.put(arr, 2, Array.negative_inf())
end)
}
end
def put(
%{
bit_vector: bit_vector,
size: size,
offset: offset,
lower_bound: lower_bound,
upper_bound: upper_bound
} = set,
element
)
when is_integer(element) do
if element < lower_bound or element > upper_bound do
throw(:value_out_of_bounds)
end
position = value_to_offset(offset, element)
if member_impl(set, position) do
:ok
else
:bit_vector.set(bit_vector, position)
update_min(set, element)
update_max(set, element)
Array.update(size, 1, fn current_size -> current_size + 1 end)
end
end
def delete(%{offset: offset, bit_vector: bit_vector, size: size} = set, element)
when is_integer(element) do
position = value_to_offset(offset, element)
if !member_impl(set, position) do
:ok
else
:bit_vector.clear(bit_vector, position)
maybe_tighten_min(set, element)
maybe_tighten_max(set, element)
Array.update(size, 1, fn current_size -> current_size - 1 end)
end
end
def member?(%{offset: offset} = set, element) when is_integer(element) do
offset_value = value_to_offset(offset, element)
member_impl(set, offset_value)
end
defp member_impl(%{bit_vector: bit_vector} = _set, offset_value) do
:bit_vector.get(bit_vector, offset_value) == 1
end
defp offset_to_value(offset, value) do
value - offset
end
defp value_to_offset(offset, value) do
value + offset
end
def iterate(set, acc, reducer) when is_function(reducer, 2) do
iterate_impl(set, acc, first_position(set), reducer)
end
defp iterate_impl(_set, acc, nil, _reducer) do
acc
end
defp iterate_impl(set, acc, position, reducer) do
case reducer.(value_at_position(set, position), acc) do
{:halt, acc2} -> acc2
{:cont, acc2} -> iterate_impl(set, acc2, next_position(set, position), reducer)
acc2 -> iterate_impl(set, acc2, next_position(set, position), reducer)
end
end
def value_at_position(set, {block_idx, block_offset, _}) do
value_at_position(set, block_idx, block_offset)
end
def value_at_position(set, {block_idx, block_offset}) do
value_at_position(set, block_idx, block_offset)
end
def value_at_position(%{offset: offset} = _set, block_idx, block_offset) do
offset_to_value(
offset,
(block_idx - 1) * 64 + block_offset
)
end
defp first_position(set) do
if size(set) > 0 do
next_position(set, 1, :lsb)
end
end
defp last_block(%{offset: value_offset} = set) do
case max(set) do
nil -> 0
max_value ->
value_address(max_value + value_offset)
|> elem(0)
end
end
defp get_block(%{bit_vector: {:bit_vector, atomics}} = _set, block_idx) do
## Note: we do not use Array.get/2 here because of possible mix-up
## between 0s and how Array differentiates between 0s and `nil`s
:atomics.get(atomics, block_idx)
end
@doc """
Find next position for {block_idx, block_offset}
"""
def next_position(set, {block_idx, block_offset} = _value_position) do
next_position(set, {block_idx, block_offset, nil})
end
def next_position(set, {block_idx, block_offset, block_value} = _value_position) do
next_position(set, block_idx, block_offset, block_value)
end
def next_position(set, block_idx, block_offset) do
next_position(set, block_idx, block_offset, nil)
end
def next_position(set, block_idx, block_offset, block_value) do
if block_idx <= last_block(set) do
next_position_impl(set, block_idx, block_offset, block_value)
end
end
## The beginning of a new block
def next_position_impl(set, block_idx, :lsb, _block_value) do
case get_block(set, block_idx) do
0 ->
next_position(set, block_idx + 1, :lsb)
block_value ->
case lsb(block_value) do
nil ->
next_position(set, block_idx + 1, :lsb)
lsb ->
{block_idx, lsb, block_value}
end
end
end
def next_position_impl(
set,
block_idx,
block_offset,
block_value
) do
case block_value || get_block(set, block_idx) do
0 ->
## nothing in this block, try next one.
next_position(set, block_idx + 1, :lsb)
block_value ->
## Reset all leftmost bits up to block_offset
## Take LSB - this will give an offset of next bit set to 1
shift = block_offset + 1
block_tail = block_value >>> shift
case block_tail |> lsb() do
nil ->
next_position(set, block_idx + 1, :lsb)
new_block_offset ->
{block_idx, new_block_offset + shift, block_value}
end
end
end
## Find the index of atomics where the `n` value resides
defp block_index(value) do
div(value, 64) + 1
end
## Given the value, find block index (that is a position in bit vector)
## and an offset of the value relative to the beginning of the block
def value_address(value) do
{block_index(value), rem(value, 64)}
end
def to_list(set) do
iterate(set, [], fn val, acc -> [val | acc] end) |> Enum.reverse()
end
def size(%{size: size} = _set) do
Array.get(size, 1)
end
def empty?(set) do
size(set) == 0
end
def subset?(set1, set2) do
size1 = size(set1)
size2 = size(set2)
if size1 > size2 do
false
else
iterate(set1, true, fn val, _acc ->
(member?(set2, val) && {:cont, true}) || {:halt, false}
end)
end
end
def equal?(set1, set2) do
size(set1) == size(set2) && subset?(set1, set2)
end
def disjoint?(set1, set2) do
size1 = size(set1)
size2 = size(set2)
if size1 == 0 || size2 == 0 do
true
else
{smaller, bigger} = (size1 < size2 && {set1, set2}) || {set2, set1}
iterate(smaller, true, fn value, _ ->
(member?(bigger, value) && {:halt, false}) || {:cont, true}
end)
end
end
def intersection(set1, set2) do
lb = min(set1.lower_bound, set2.lower_bound)
ub = max(set1.upper_bound, set2.upper_bound)
{smaller, bigger} = (size(set1) < size(set2) && {set1, set2}) || {set2, set1}
new(lb, ub)
|> tap(fn intersection_set ->
iterate(smaller, nil, fn val, _ ->
member?(bigger, val) && put(intersection_set, val)
end)
end)
end
def union(set1, set2) do
lb = min(set1.lower_bound, set2.lower_bound)
ub = max(set1.upper_bound, set2.upper_bound)
new(lb, ub)
|> tap(fn union_set ->
iterate(set1, nil, fn val, _ -> put(union_set, val) end)
iterate(set2, nil, fn val, _ -> put(union_set, val) end)
end)
end
def difference(set1, set2) do
filter(set1, fn val -> !member?(set2, val) end)
end
def symmetric_difference(set1, set2) do
lb = min(set1.lower_bound, set2.lower_bound)
ub = max(set1.upper_bound, set2.upper_bound)
new(lb, ub)
|> tap(fn sym_diff_set ->
iterate(set1, nil, fn val, _ -> put(sym_diff_set, val) end)
iterate(set2, nil, fn val, _ ->
member?(sym_diff_set, val) && delete(sym_diff_set, val) || put(sym_diff_set, val)
end)
end)
end
def min(set) do
if size(set) > 0 do
min_impl(set)
end
end
defp min_impl(%{minmax: minmax} = _set) do
Array.get(minmax, 1)
end
defp update_min(%{minmax: minmax} = set, value) do
if min_impl(set) > value do
Array.put(minmax, 1, value)
end
:ok
end
defp maybe_tighten_min(%{minmax: minmax} = set, removed_value) do
if min_impl(set) == removed_value do
Array.put(minmax, 1, find_min(set, removed_value) || Array.inf())
end
end
defp find_min(
%{offset: value_offset, last_index: last_idx} = set,
starting_value
) do
{min_block_idx, _min_block_offset} = value_address(starting_value + value_offset)
_new_min_value =
Enum.reduce_while(min_block_idx..last_idx, nil, fn block_idx, acc ->
case get_block(set, block_idx) do
0 ->
{:cont, acc}
block_value ->
{:halt, value_at_position(set, block_idx, lsb(block_value))}
end
end)
end
def max(set) do
if size(set) > 0 do
max_impl(set)
end
end
defp max_impl(%{minmax: minmax} = _set) do
Array.get(minmax, 2)
end
defp update_max(%{minmax: minmax} = set, value) do
if max_impl(set) < value do
Array.put(minmax, 2, value)
end
:ok
end
defp maybe_tighten_max(%{minmax: minmax} = set, removed_value) do
if max_impl(set) == removed_value do
Array.put(minmax, 2, find_max(set, removed_value) || Array.negative_inf())
end
:ok
end
defp find_max(%{offset: value_offset} = set, starting_value) do
{max_block_idx, _max_block_offset} = value_address(starting_value + value_offset)
_new_max_value =
Enum.reduce_while(1..max_block_idx, nil, fn block_idx, acc ->
case get_block(set, block_idx) do
0 ->
{:cont, acc}
block_value ->
{:halt, value_at_position(set, block_idx, msb(block_value))}
end
end)
end
def next(%{offset: value_offset} = set, element) do
current_position = value_address(element + value_offset)
case next_position(set, current_position) do
nil ->
nil
{block_id, block_offset, _block_value} = _next_pos ->
value_at_position(set, block_id, block_offset)
end
end
def reduce(set, acc, reducer) when is_function(reducer, 2) do
iterate(set, acc, reducer)
end
def filter(set, filter_fun) when is_function(filter_fun, 1) do
new(set.lower_bound, set.upper_bound)
|> tap(fn f_set ->
iterate(set, nil, fn val, _ ->
filter_fun.(val) && put(f_set, val)
end)
end)
end
end