Current section

Files

Jump to
bisect lib bisect.ex
Raw

lib/bisect.ex

defmodule Bisect do
@moduledoc File.read!("README.md")
import Bitwise,
only: [
>>>: 2
]
@doc ~S"""
Executes binary search in list `enumerable` by passing list elements
to the `function` for comparison, assuming the list is sorted.
### Options
- `access_keys`: Specifies path of value in `term`,
to be passed to the `function` for comparison.
See `Kernel.get_in/2` function and `Access.fetch/2` callback
for more information about access keys.
### Examples
iex> Bisect.binary_search([1, 2], fn term ->
...> term >= 1
...> end)
0
iex> Bisect.binary_search([1, 2], fn term ->
...> term > 1
...> end)
1
iex> Bisect.binary_search([2, 1], fn term ->
...> term < 0
...> end)
2
iex> Bisect.binary_search([%{value: 1}, %{value: 2}], fn term ->
...> term > 1
...> end, access_keys: [:value])
1
"""
@doc since: "0.2.0"
@spec binary_search(Enum.t(), (term -> boolean), keyword) :: non_neg_integer
def binary_search(enumerable, function, opts \\ []) do
do_binary_search(enumerable, function, 0, length(enumerable), opts)
end
defp do_binary_search(enumerable, function, low, high, opts)
when low < high do
middle = (low + high) >>> 0x1
element = Enum.at(enumerable, middle)
case apply_traversal(function, element, opts[:access_keys]) do
true ->
do_binary_search(enumerable, function, low, middle, opts)
false ->
do_binary_search(enumerable, function, middle + 1, high, opts)
end
end
defp do_binary_search(_enumerable, _function, low, _high, _opts) do
low
end
defp apply_traversal(function, element, access_keys) do
apply(function, [access_value(element, access_keys)])
end
defp access_value(element, access_keys)
when access_keys in [nil, []] do
element
end
defp access_value(element, access_keys) do
get_in(element, access_keys)
end
@doc ~S"""
Returns the leftmost index where to insert `term` in list `enumerable`,
assuming the list is sorted.
### Examples
iex> Bisect.bisect_left([1, 2], 1)
0
iex> Bisect.bisect_left([1, 2], 2)
1
iex> Bisect.bisect_left([1, 2], 4)
2
See `Bisect.binary_search/3` for options.
"""
@doc since: "0.1.0"
@spec bisect_left(Enum.t(), term, keyword) :: non_neg_integer
def bisect_left(enumerable, term, opts \\ []) do
access_keys = opts[:access_keys]
binary_search(
enumerable,
fn element ->
element >= access_value(term, access_keys)
end,
opts
)
end
@doc ~S"""
Returns the rightmost index where to insert `term` in list `enumerable`,
assuming the list is sorted.
### Examples
iex> Bisect.bisect_right([1, 2], 1)
1
iex> Bisect.bisect_right([1, 2, 2, 4], 4)
4
iex> Bisect.bisect_right([2, 4], 0)
0
See `Bisect.binary_search/3` for options.
"""
@doc since: "0.1.0"
@spec bisect_right(Enum.t(), term, keyword) :: non_neg_integer
def bisect_right(enumerable, term, opts \\ []) do
access_keys = opts[:access_keys]
binary_search(
enumerable,
fn element ->
element > access_value(term, access_keys)
end,
opts
)
end
@doc ~S"""
Inserts `term` into list `enumerable`, and keeps it sorted
assuming the list is already sorted.
If `term` is already in `enumerable`, inserts it to the left of the leftmost `term`.
### Examples
iex> Bisect.insort_left([1, 2], 1)
[1, 1, 2]
iex> Bisect.insort_left([1, 2, 2, 4], 4)
[1, 2, 2, 4, 4]
iex> Bisect.insort_left([2, 4], 0)
[0, 2, 4]
iex> Bisect.insort_left([%{value: 2}, %{value: 4}], %{value: 0}, access_keys: [:value])
[%{value: 0}, %{value: 2}, %{value: 4}]
See `Bisect.binary_search/3` for options.
"""
@doc since: "0.1.0"
@spec insort_left(Enum.t(), term, keyword) :: Enum.t()
def insort_left(enumerable, term, opts \\ []) do
index = bisect_left(enumerable, term, opts)
List.insert_at(enumerable, index, term)
end
@doc ~S"""
Inserts `term` into list `enumerable`, and keeps it sorte
assuming the list is already sorted.
If `term` is already in `enumerable`, inserts it to the right of the rightmost `term`.
### Examples
iex> Bisect.insort_right([1, 2], 1)
[1, 1, 2]
iex> Bisect.insort_right([1, 2, 2, 4], 4)
[1, 2, 2, 4, 4]
iex> Bisect.insort_right([2, 4], 0)
[0, 2, 4]
iex> Bisect.insort_right([%{value: 2}, %{value: 4}], %{value: 0}, access_keys: [:value])
[%{value: 0}, %{value: 2}, %{value: 4}]
See `Bisect.binary_search/3` for options.
"""
@doc since: "0.1.0"
@spec insort_right(Enum.t(), term, keyword) :: Enum.t()
def insort_right(enumerable, term, opts \\ []) do
index = bisect_right(enumerable, term, opts)
List.insert_at(enumerable, index, term)
end
end