lib/data_structures/heap.ex

defmodule Heap do
  @moduledoc """
	Documentation for `Heap` data structure
	"""

	@typedoc """
   Heap - data structure
   """
	@type heap :: list(integer)

  @doc """
  Create new, empty Heap

  ## Examples

			iex> Heap.new()
			[]

  """
	@spec new :: []
  def new, do: []

  @doc """
  Checks if heap is empty

  ## Examples

			iex> Heap.empty?([1, 2, 3])
			false

  """
	@spec empty?(heap) :: boolean
  def empty?(heap), do: length(heap) == 0

	@doc """
  Checks if the passed integer is a member of the heap

  ## Examples

			iex> Heap.member?([1, 2, 3], 2)
			true

			iex> Heap.member?([1, 2, 3], 5)
			false

  """
	@spec member?(heap, integer) :: boolean
  def member?(heap, value), do: Enum.member?(heap, value)

	@doc """
  Returns current size of heap

  ## Examples

			iex> Heap.size([1, 2, 3])
			3

			iex> Heap.size([])
			0

  """
	@spec size(heap) :: non_neg_integer
  def size(heap), do: length(heap)

	@doc """
  Returns the root value of the heap. Assume the passed heap has the max or min heap properties.

  ## Examples

			iex> Heap.root([16, 14, 10, 8, 7, 9, 3, 2, 4, 1])
			16

			iex> Heap.root([44, 35, 42, 33, 31, 19, 27, 10, 26, 14])
			44

			iex> Heap.root([3, 8, 10, 15, 10, 12])
			3

  """
	@spec root(heap) :: integer | nil
  def root(heap), do: List.first(heap)

  @doc """
  Returns parent index.

  ## Examples

			iex> Heap.parent(7)
			3

			iex> Heap.parent(0)
			nil

  """
	@spec parent(integer) :: nil | non_neg_integer
	def parent(index) do
		leaf = Integer.floor_div(index - 1, 2)

		if leaf >= 0, do: leaf, else: nil
	end

  @doc """
  Returns index of left leaf.

  ## Examples

			iex> Heap.left(2)
			5

			iex> Heap.left(4)
			9

  """
	@spec left(non_neg_integer) :: non_neg_integer
	def left(index), do: index * 2 + 1

  @doc """
  Returns index of right leaf.

  ## Examples

			iex> Heap.right(2)
			6

  """
	@spec right(non_neg_integer) :: non_neg_integer
  def right(index), do: index * 2 + 2

	@doc """
		Method complexity: O(lgn)

		Max heapify changes list of elements in max heap. There is property for every element in heap:

		Heap[parent(i)] >= Heap[i]

  ## Examples

			iex> Heap.max_heapify([16, 4, 10, 14, 7, 9, 3, 2, 8, 1], 1)
			[16, 14, 10, 8, 7, 9, 3, 2, 4, 1]

  """
	@spec max_heapify(heap, integer, integer | nil) :: heap
	def max_heapify(heap, index, len \\ nil) do
		heap_size = if len != nil, do: len, else: length(heap)
		largest = max_heap(heap, index, heap_size)

		if largest != index do
			swap(List.to_tuple(heap), index, largest)
				|> Tuple.to_list
				|> max_heapify(largest, heap_size)
		else
			heap
		end
	end


	@doc """
		Method complexity: O(lgn)

		Min heapify changes list of elements in max heap. There is property for every element in heap:

		Heap[parent(i)] <= Heap[i]

  ## Examples

			iex> Heap.min_heapify([1, 4, 10, 14, 7, 9, 3, 2, 8, 16], 1)
			[1, 4, 10, 14, 7, 9, 3, 2, 8, 16]

  """
	@spec min_heapify(heap, integer, integer | nil) :: heap
	def min_heapify(heap, index, len \\ nil) do
		heap_size = if len != nil, do: len, else: length(heap)
		largest = min_heap(heap, index, heap_size)

		if largest != index do
			swap(List.to_tuple(heap), index, largest)
				|> Tuple.to_list
				|> min_heapify(largest)
		else
			heap
		end
	end

	@doc """
		Method complexity: O(n)

		Transforms passed heap (list) into specified heap type
		 - min: (Heap[parent(i)] <= Heap[i])
		 - max: (Heap[parent(i)] >= Heap[i])

  ## Examples

			iex> Heap.build_heap([4, 1, 3, 2, 16, 9, 10, 14, 8, 7], :max)
			[16, 14, 10, 8, 7, 9, 3, 2, 4, 1]

			iex> Heap.build_heap([16, 4, 9, 14, 7, 10, 3, 2, 8, 1], :max)
			[16, 14, 10, 8, 7, 9, 3, 2, 4, 1]

			iex> Heap.build_heap([16, 4, 9, 14, 7, 10, 3, 2, 8, 1], :min)
			[1, 2, 3, 8, 4, 10, 9, 14, 16, 7]

  """
	@spec build_heap(heap, atom) :: heap
	def build_heap(heap, type) do
		leaf = Integer.floor_div(length(heap), 2) - 1
		if type == :max,
		do: build_heap(max_heapify(heap, leaf), type, leaf),
		else: build_heap(min_heapify(heap, leaf), type, leaf)
	end

	defp build_heap(heap, type, i) when i >= 0 do
		if type == :max,
		do: build_heap(max_heapify(heap, i), type, i - 1),
		else: build_heap(min_heapify(heap, i), type, i - 1)
	end

	defp build_heap(heap, _type, _i), do: heap

	@doc """
		Method complexity: O(n)

		Sorts the heap (from min to max)
		
  ## Examples

			iex> Heap.sort([4, 1, 3, 2, 16, 9, 10, 14, 8, 7])
			[1, 2, 3, 4, 7, 8, 9, 10, 14, 16]

  """
	@spec sort(heap) :: heap
	def sort(heap) do
		max_heap = build_heap(heap, :max)
		heap_size = size(max_heap)

		max_heap |> sort(heap_size)
	end

	defp sort(max_heap, j) when j <= 1, do: max_heap

	defp sort(max_heap, j) do
		i = j - 1

		swap(List.to_tuple(max_heap), 0, i)
			|> Tuple.to_list
			|> max_heapify(0, i)
			|> sort(i)
	end

	@spec max_heap(heap, integer, integer) :: integer
	defp max_heap(heap, index, len) do
		l = left(index)
		r = right(index)
		largest = if (
			l < len &&
			Enum.at(heap, l) > Enum.at(heap, index)
		), do: l, else: index

		if (
			r < len &&
			Enum.at(heap, r) > Enum.at(heap, largest)
		), do: r, else: largest
	end

	@spec min_heap(heap, integer, integer) :: integer
	defp min_heap(heap, index, len) do
		l = left(index)
		r = right(index)
		largest = if (
			l < len &&
			Enum.at(heap, l) < Enum.at(heap, index)
		), do: l, else: index

		if (
			r < len &&
			Enum.at(heap, r) < Enum.at(heap, largest)
		), do: r, else: largest
	end

	defp swap(heap, i, j) do
    {vi, vj} = {elem(heap, i), elem(heap, j)}
    heap |> put_elem(i, vj) |> put_elem(j, vi)
  end

end