-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathinterval_test.exs
149 lines (119 loc) · 4.22 KB
/
interval_test.exs
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
defmodule Daat.Examples.IntervalTest do
use ExUnit.Case
import Daat
@moduledoc """
In this example, we define a parameterized module, Interval, which provides
a generic interface for constructing and working with intervals of arbitrary
elements.
We impose that an instance of Interval includes a module, `comparable`, which
defines a `compare/2` function. Note that this is the same interface used by
Elixir's built-in sorting functions, as of Elixir 1.10. As a result, we are
able to easily instantiate the module using the `Date` or `DateTime` modules,
in order to define a module meant for working with intervals or `Date` or
`DateTime` elements respectively.
I don't do it in this example, but if we were to define property tests for the
abstract Interval pmodule, we would then be able to easily test that instances
of the Interval pmodule pass those same tests.
"""
defmodule Comparable do
@callback compare(term, term) :: :lt | :eq | :gt
end
defmodule ComparableNumber do
def compare(a, b) do
cond do
a < b -> :lt
a == b -> :eq
a > b -> :gt
end
end
end
defpmodule Interval, comparable: Comparable do
@type t ::
{term, term}
| :empty
def create(low, high) do
if comparable().compare(low, high) == :gt do
:empty
else
{low, high}
end
end
def empty?(:empty), do: true
def empty?(_), do: false
def contains?(:empty, _), do: false
def contains?({low, high}, item) do
compare_low = comparable().compare(item, low)
compare_high = comparable().compare(item, high)
case {compare_low, compare_high} do
{:eq, _} -> true
{_, :eq} -> true
{:gt, :lt} -> true
_ -> false
end
end
def intersect(t1, t2) do
min = fn x, y ->
case comparable().compare(x, y) do
:lt -> x
_ -> y
end
end
max = fn x, y ->
case comparable().compare(x, y) do
:gt -> x
_ -> y
end
end
case {t1, t2} do
{:empty, _} ->
:empty
{_, :empty} ->
:empty
{{l1, h1}, {l2, h2}} ->
create(max.(l1, l2), min.(h1, h2))
end
end
end
definst(Interval, DateInterval, comparable: Date)
definst(Interval, DateTimeInterval, comparable: DateTime)
definst(Interval, NumberInterval, comparable: ComparableNumber)
test "contains?/2 works with Dates" do
today = Date.utc_today()
yesterday = Date.add(today, -1)
tomorrow = Date.add(today, 1)
ereyesterday = Date.add(today, -2)
overmorrow = Date.add(today, 2)
interval = DateInterval.create(yesterday, tomorrow)
assert true == DateInterval.contains?(interval, yesterday)
assert true == DateInterval.contains?(interval, today)
assert true == DateInterval.contains?(interval, tomorrow)
assert false == DateInterval.contains?(interval, ereyesterday)
assert false == DateInterval.contains?(interval, overmorrow)
end
test "contains?/2 works with DateTimes" do
datetime1 = DateTime.utc_now()
datetime2 = DateTime.add(datetime1, 1)
datetime3 = DateTime.add(datetime1, 2)
datetime4 = DateTime.add(datetime1, 3)
datetime5 = DateTime.add(datetime1, 4)
interval = DateTimeInterval.create(datetime2, datetime4)
assert true == DateTimeInterval.contains?(interval, datetime2)
assert true == DateTimeInterval.contains?(interval, datetime3)
assert true == DateTimeInterval.contains?(interval, datetime4)
assert false == DateTimeInterval.contains?(interval, datetime1)
assert false == DateTimeInterval.contains?(interval, datetime5)
end
test "contains?/2 works with numbers" do
interval = NumberInterval.create(0, 10)
assert true == NumberInterval.contains?(interval, 0)
assert true == NumberInterval.contains?(interval, 5)
assert true == NumberInterval.contains?(interval, 10)
assert false == NumberInterval.contains?(interval, -1)
assert false == NumberInterval.contains?(interval, 11)
end
test "intersect/2" do
interval1 = NumberInterval.create(0, 10)
interval2 = NumberInterval.create(3, 15)
assert {3, 10} == NumberInterval.intersect(interval1, interval2)
end
end