Current section
Files
Jump to
Current section
Files
lib/flat_tree.ex
defmodule FlatTree do
@moduledoc """
You can represent a binary tree in a simple flat list using the following structure:
3
1 5
0 2 4 6 ...
`FlatTree` exposes a series of functions to help you build and maintain this data structure.
import FlatTree
list = []
i = FlatTree.index(0, 0) # get array index for depth: 0, offset: 0
j = FlatTree.index(1, 0) # get array index for depth: 1, offset: 0
# use these indexes to store some data
list = List.insert_at(list, i, "a")
list = List.insert_at(list, j, "b")
list = List.insert_at(list, FlatTree.parent(i), "parent of a and b")
"""
use Bitwise, only_operators: true
import Integer, only: [floor_div: 2]
@doc """
Returns a tuple {left_child, right_child} with the indexes of this elements
children. If this element does not have any children is returns `nil`.
## Examples
iex> FlatTree.children(0)
nil
iex> FlatTree.children(3)
{1, 5}
"""
@spec children(index :: integer, depth :: integer | nil) :: {integer, integer}
def children(index, depth \\ nil)
def children(index, _) when is_integer(index) and (index &&& 1) == 0, do: nil
def children(index, depth) when is_nil(depth), do: children(index, depth(index))
def children(index, depth) when is_integer(index) and is_integer(depth) do
offset = offset(index, depth) * 2
{index(depth - 1, offset), index(depth - 1, offset + 1)}
end
@doc """
Returns how many nodes (including parent nodes) a tree contains.
## Examples
iex> FlatTree.count(1)
3
"""
@spec count(index :: integer, depth :: integer | nil) :: integer
def count(index, depth \\ nil)
def count(index, _) when is_integer(index) and (index &&& 1) == 0, do: 1
def count(index, depth) when is_nil(depth), do: count(index, depth(index))
def count(index, depth) when is_integer(index) and is_integer(depth) do
two_pow(depth + 1) - 1
end
@doc """
Returns the depth of an element.
## Examples
iex> FlatTree.depth(0)
0
iex> FlatTree.depth(3)
2
"""
@spec depth(index :: integer, depth :: integer) :: integer
def depth(index, depth \\ 0), do: do_depth(index + 1, depth)
@doc """
Returns a list of all the full roots (subtrees where all nodes have either 2
or 0 children) < index. For example `full_roots(8)` returns `[3]` since the
subtree rooted at `3` spans `0 -> 6` and the tree rooted at `7` has a child
located at `9` which is `>= 8`.
## Examples
iex> FlatTree.full_roots(0)
[]
iex> FlatTree.full_roots(18)
[7, 16]
"""
@spec full_roots(index :: integer, result :: [integer]) :: [integer]
def full_roots(index, result \\ [])
def full_roots(index, _) when is_integer(index) and (index &&& 1) == 1 do
raise("You can only look up roots for depth(0) blocks")
end
def full_roots(index, result) do
index = floor_div(index, 2)
do_full_roots(result, index)
end
@doc """
Returns an array index for the tree element at the given depth and offset.
## Examples
iex> FlatTree.index(0, 0)
0
iex> FlatTree.index(0, 2)
4
"""
@spec index(depth :: integer, offset :: integer) :: integer
def index(depth, offset) when is_integer(depth) and is_integer(offset) do
(1 + 2 * offset) * two_pow(depth) - 1
end
@doc """
Returns the index of the left child of this element. If this element does not
have a left child it returns `-1`.
## Examples
iex> FlatTree.left_child(0)
-1
iex> FlatTree.left_child(3)
1
"""
@spec left_child(index :: integer, depth :: integer | nil) :: integer
def left_child(index, depth \\ nil)
def left_child(index, _) when (index &&& 1) == 0, do: -1
def left_child(index, depth) when is_nil(depth), do: left_child(index, depth(index))
def left_child(index, depth) when is_integer(index) and is_integer(depth) do
index(depth - 1, offset(index, depth) * 2)
end
@doc """
Returns the left spanning in index in the tree `index` spans.
## Examples
iex> FlatTree.left_span(0)
0
iex> FlatTree.left_span(23)
16
"""
@spec left_span(index :: integer, depth :: integer | nil) :: integer
def left_span(index, depth \\ nil)
def left_span(index, _) when (index &&& 1) == 0, do: index
def left_span(index, depth) when is_nil(depth), do: left_span(index, depth(index))
def left_span(index, depth) when is_integer(index) and is_integer(depth) do
offset(index, depth) * two_pow(depth + 1)
end
@doc """
Returns the relative offset of an element.
## Examples
iex> FlatTree.offset(1)
0
iex> FlatTree.offset(4)
2
"""
@spec offset(index :: integer, depth :: integer | nil) :: integer
def offset(index, depth \\ nil)
def offset(index, _) when (index &&& 1) == 0, do: floor_div(index, 2)
def offset(index, depth) when is_nil(depth), do: offset(index, depth(index))
def offset(index, depth) when is_integer(index) and is_integer(depth) do
(index + 1)
|> floor_div(two_pow(depth))
|> Kernel.-(1)
|> floor_div(2)
end
@doc """
Returns the index of the parent element in tree.
## Examples
iex> FlatTree.parent(0)
1
iex> FlatTree.parent(1)
3
"""
@spec parent(index :: integer, depth :: integer | nil) :: integer
def parent(index, depth \\ nil)
def parent(index, depth) when is_nil(depth), do: parent(index, depth(index))
def parent(index, depth) when is_integer(index) and is_integer(depth) do
offset = offset(index, depth)
index(depth + 1, right_shift(offset))
end
@doc """
Returns the index of the right child of this element. If this element does not
have a right child it returns `-1`.
## Examples
iex> FlatTree.right_child(0)
-1
iex> FlatTree.right_child(3)
5
"""
@spec right_child(index :: integer, depth :: integer | nil) :: integer
def right_child(index, depth \\ nil)
def right_child(index, _) when (index &&& 1) == 0, do: -1
def right_child(index, depth) when is_nil(depth), do: right_child(index, depth(index))
def right_child(index, depth) when is_integer(index) and is_integer(depth) do
index(depth - 1, 1 + offset(index, depth) * 2)
end
@doc """
Returns the right spanning in index in the tree `index` spans.
## Examples
iex> FlatTree.right_span(0)
0
iex> FlatTree.right_span(23)
30
"""
@spec right_span(index :: integer, depth :: integer | nil) :: integer
def right_span(index, depth \\ nil)
def right_span(index, _) when (index &&& 1) == 0, do: index
def right_span(index, depth) when is_nil(depth), do: right_span(index, depth(index))
def right_span(index, depth) when is_integer(index) and is_integer(depth) do
(offset(index, depth) + 1) * two_pow(depth + 1) - 2
end
@doc """
Return the index of this elements sibling.
## Examples
iex> FlatTree.sibling(0)
2
iex> FlatTree.sibling(2)
0
"""
@spec sibling(index :: integer, depth :: integer | nil) :: integer
def sibling(index, depth \\ nil)
def sibling(index, depth) when is_nil(depth), do: sibling(index, depth(index))
def sibling(index, depth) when is_integer(index) and is_integer(depth) do
offset = offset(index, depth)
offset = if (offset &&& 1) == 1, do: offset - 1, else: offset + 1
index(depth, offset)
end
@doc """
Returns the range (inclusive) the tree root at `index` spans.
## Examples
iex> FlatTree.spans(0)
{0, 0}
iex> FlatTree.spans(3)
{0, 6}
"""
@spec spans(index :: integer, depth :: integer | nil) :: {integer, integer}
def spans(index, depth \\ nil)
def spans(index, _) when is_integer(index) and (index &&& 1) == 0, do: {index, index}
def spans(index, depth) when is_nil(depth), do: spans(index, depth(index))
def spans(index, depth) when is_integer(index) and is_integer(depth) do
offset = offset(index, depth)
width = two_pow(depth + 1)
{offset * width, (offset + 1) * width - 2}
end
@spec do_depth(index :: integer, depth :: integer) :: integer
defp do_depth(index, depth) when (index &&& 1) == 1, do: depth
defp do_depth(index, depth), do: index |> right_shift() |> do_depth(depth + 1)
@spec do_full_roots(result :: [integer], index :: integer, offset :: integer, factor :: integer) ::
[integer]
defp do_full_roots(result, index, offset \\ 0, factor \\ 1)
defp do_full_roots(result, index, _, _) when is_list(result) and index == 0 do
Enum.reverse(result)
end
defp do_full_roots(result, index, offset, factor)
when is_list(result) and is_integer(index) and is_integer(offset) and is_integer(factor) do
factor = get_factor(index, factor)
result = [offset + factor - 1 | result]
offset = offset + 2 * factor
index = index - factor
do_full_roots(result, index, offset)
end
@spec get_factor(index :: integer, factor :: integer) :: integer
defp get_factor(index, factor) when factor * 2 > index, do: factor
defp get_factor(index, factor) when is_integer(index) and is_integer(factor) do
get_factor(index, factor * 2)
end
@spec right_shift(n :: integer) :: integer
defp right_shift(n), do: floor_div(n - (n &&& 1), 2)
@spec two_pow(n :: integer) :: integer
defp two_pow(n) when n < 31, do: 1 <<< n
defp two_pow(n), do: (1 <<< 30) * (1 <<< (n - 30))
end