Current section
Files
Jump to
Current section
Files
lib/ex_range.ex
defmodule A.ExRange do
@moduledoc false
@type t :: %__MODULE__{start: integer, stop: integer}
@enforce_keys [:start, :stop]
defstruct [:start, :stop]
# TODO remove in 0.6
@doc false
@deprecated "Use first..last//1 instead"
def new(start \\ 0, stop)
def new(start, stop) when is_integer(start) and is_integer(stop) do
%A.ExRange{start: start, stop: stop}
end
def new(start, stop) do
raise ArgumentError,
"A.ExRange (start ~> stop) expect both sides to be integers, " <>
"got: #{inspect(start)} ~> #{inspect(stop)}"
end
@doc """
Checks if two ranges are disjoint.
## Examples
iex> A.ExRange.disjoint?(1 ~> 6, 6 ~> 9)
true
iex> A.ExRange.disjoint?(6 ~> 1, 6 ~> 9)
true
iex> A.ExRange.disjoint?(1 ~> 6, 5 ~> 9)
false
iex> A.ExRange.disjoint?(1 ~> 6, 2 ~> 7)
false
"""
@spec disjoint?(t, t) :: boolean
def disjoint?(%__MODULE__{start: start1, stop: stop1}, %__MODULE__{start: start2, stop: stop2}) do
{start1, stop1} = normalize(start1, stop1)
{start2, stop2} = normalize(start2, stop2)
stop2 < start1 + 1 or stop1 < start2 + 1
end
@compile inline: [normalize: 2]
defp normalize(start, stop) when start > stop, do: {stop, start}
defp normalize(start, stop), do: {start, stop}
defimpl Enumerable do
def reduce(%A.ExRange{start: start, stop: stop}, acc, fun) do
reduce(start, stop, acc, fun, _up? = stop >= start)
end
defp reduce(_start, _stop, {:halt, acc}, _fun, _up?) do
{:halted, acc}
end
defp reduce(start, stop, {:suspend, acc}, fun, up?) do
{:suspended, acc, &reduce(start, stop, &1, fun, up?)}
end
defp reduce(start, stop, {:cont, acc}, fun, _up? = true) when start < stop do
reduce(start + 1, stop, fun.(start, acc), fun, _up? = true)
end
defp reduce(start, stop, {:cont, acc}, fun, _up? = false) when start > stop do
reduce(start - 1, stop, fun.(start, acc), fun, _up? = false)
end
defp reduce(_, _, {:cont, acc}, _fun, _up) do
{:done, acc}
end
def member?(%A.ExRange{start: start, stop: stop}, value) when is_integer(value) do
if start <= stop do
{:ok, start <= value and value < stop}
else
{:ok, stop < value and value <= start}
end
end
def member?(%A.ExRange{}, _value) do
{:ok, false}
end
def count(%A.ExRange{start: start, stop: stop}) do
if start <= stop do
{:ok, stop - start}
else
{:ok, start - stop}
end
end
def slice(%A.ExRange{start: start, stop: stop}) do
if start <= stop do
{:ok, stop - start, &slice_asc(start + &1, &2)}
else
{:ok, start - stop, &slice_desc(start - &1, &2)}
end
end
defp slice_asc(current, 1), do: [current]
defp slice_asc(current, remaining), do: [current | slice_asc(current + 1, remaining - 1)]
defp slice_desc(current, 1), do: [current]
defp slice_desc(current, remaining), do: [current | slice_desc(current - 1, remaining - 1)]
end
defimpl Inspect do
import Inspect.Algebra
def inspect(%A.ExRange{start: start, stop: stop}, opts) do
concat([
"#A<",
to_doc(start, opts),
" ~> ",
to_doc(stop, opts),
">"
])
end
end
end