126 lines
2.5 KiB
Elixir
126 lines
2.5 KiB
Elixir
defmodule Range do
|
|
@moduledoc """
|
|
Defines a Range.
|
|
|
|
A Range is represented internally as a struct. However,
|
|
the most common form of creating and matching on ranges
|
|
is via the `../2` macro, auto-imported from Kernel:
|
|
|
|
iex> range = 1..3
|
|
1..3
|
|
iex> first .. last = range
|
|
iex> first
|
|
1
|
|
iex> last
|
|
3
|
|
|
|
"""
|
|
|
|
defstruct first: nil, last: nil
|
|
|
|
@type t :: %Range{}
|
|
@type t(first, last) :: %Range{first: first, last: last}
|
|
|
|
@doc """
|
|
Creates a new range.
|
|
"""
|
|
def new(first, last) do
|
|
%Range{first: first, last: last}
|
|
end
|
|
|
|
@doc """
|
|
Returns true if the given argument is a range.
|
|
|
|
## Examples
|
|
|
|
iex> Range.range?(1..3)
|
|
true
|
|
|
|
iex> Range.range?(0)
|
|
false
|
|
|
|
"""
|
|
def range?(%Range{}), do: true
|
|
def range?(_), do: false
|
|
end
|
|
|
|
defprotocol Range.Iterator do
|
|
@moduledoc """
|
|
A protocol used for iterating range elements.
|
|
"""
|
|
|
|
@doc """
|
|
Returns the function that calculates the next item.
|
|
"""
|
|
def next(first, range)
|
|
|
|
@doc """
|
|
Count how many items are in the range.
|
|
"""
|
|
def count(first, range)
|
|
end
|
|
|
|
defimpl Enumerable, for: Range do
|
|
def reduce(first .. last = range, acc, fun) do
|
|
reduce(first, last, acc, fun, Range.Iterator.next(first, range), last >= first)
|
|
end
|
|
|
|
defp reduce(_x, _y, {:halt, acc}, _fun, _next, _up) do
|
|
{:halted, acc}
|
|
end
|
|
|
|
defp reduce(x, y, {:suspend, acc}, fun, next, up) do
|
|
{:suspended, acc, &reduce(x, y, &1, fun, next, up)}
|
|
end
|
|
|
|
defp reduce(x, y, {:cont, acc}, fun, next, true) when x <= y do
|
|
reduce(next.(x), y, fun.(x, acc), fun, next, true)
|
|
end
|
|
|
|
defp reduce(x, y, {:cont, acc}, fun, next, false) when x >= y do
|
|
reduce(next.(x), y, fun.(x, acc), fun, next, false)
|
|
end
|
|
|
|
defp reduce(_, _, {:cont, acc}, _fun, _next, _up) do
|
|
{:done, acc}
|
|
end
|
|
|
|
def member?(first .. last, value) do
|
|
if first <= last do
|
|
{:ok, first <= value and value <= last}
|
|
else
|
|
{:ok, last <= value and value <= first}
|
|
end
|
|
end
|
|
|
|
def count(first .. _ = range) do
|
|
{:ok, Range.Iterator.count(first, range)}
|
|
end
|
|
end
|
|
|
|
defimpl Range.Iterator, for: Integer do
|
|
def next(first, _ .. last) when is_integer(last) do
|
|
if last >= first do
|
|
&(&1 + 1)
|
|
else
|
|
&(&1 - 1)
|
|
end
|
|
end
|
|
|
|
def count(first, _ .. last) when is_integer(last) do
|
|
if last >= first do
|
|
last - first + 1
|
|
else
|
|
first - last + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
defimpl Inspect, for: Range do
|
|
import Inspect.Algebra
|
|
|
|
def inspect(first .. last, opts) do
|
|
concat [to_doc(first, opts), "..", to_doc(last, opts)]
|
|
end
|
|
end
|