1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
defmodule Range do
@moduledoc """
Defines a range.
A range represents a discrete number of values where
the first and last values are integers.
Ranges can be either increasing (first <= last) or
decresing (first > last). Ranges are also always
inclusive.
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.
"""
@spec new(integer, integer) :: t
def new(first, last) when is_integer(first) and is_integer(last) do
%Range{first: first, last: last}
end
@doc """
Returns `true` if the given `term` is a range.
## Examples
iex> Range.range?(1..3)
true
iex> Range.range?(0)
false
"""
# @spec range?(term) :: boolean
@spec range?(arg) :: false when arg: %Range{first: nil, last: nil}
@spec range?(arg) :: true when arg: %Range{}
@spec range?(term) :: false
def range?(term)
def range?(%Range{first: nil, last: nil}), do: false
def range?(%Range{}), do: true
def range?(_), do: false
end
defimpl Enumerable, for: Range do
def reduce(first .. last, acc, fun) do
validate_range!(first, last)
reduce(first, last, acc, fun, last >= first)
end
defp reduce(_x, _y, {:halt, acc}, _fun, _up) do
{:halted, acc}
end
defp reduce(x, y, {:suspend, acc}, fun, up) do
{:suspended, acc, &reduce(x, y, &1, fun, up)}
end
defp reduce(x, y, {:cont, acc}, fun, true) when x <= y do
reduce(x + 1, y, fun.(x, acc), fun, true)
end
defp reduce(x, y, {:cont, acc}, fun, false) when x >= y do
reduce(x - 1, y, fun.(x, acc), fun, false)
end
defp reduce(_, _, {:cont, acc}, _fun, _up) do
{:done, acc}
end
def member?(first .. last, value) do
validate_range!(first, last)
if first <= last do
{:ok, first <= value and value <= last}
else
{:ok, last <= value and value <= first}
end
end
def count(first .. last) do
validate_range!(first, last)
if first <= last do
{:ok, last - first + 1}
else
{:ok, first - last + 1}
end
end
defp validate_range!(first, last) when is_integer(first)
and is_integer(last),
do: :ok
defp validate_range!(first, last) do
raise ArgumentError,
"ranges (first .. last) expect both sides to be integers, " <>
"got: #{Macro.to_string({:.., [], [first, last]})}"
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
|