-
Notifications
You must be signed in to change notification settings - Fork 1
/
ranger.gleam
170 lines (160 loc) · 4.97 KB
/
ranger.gleam
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import gleam/bool
import gleam/order
import gleam/option
import gleam/iterator
type Direction {
Forward
Backward
}
/// returns a function that can be used to create a range
///
/// ## Examples
///
/// ```gleam
/// > let range =
/// > create(
/// > validate: fn(a) { string.length(a) == 1 },
/// > negate_step: fn(s) { -1 * s },
/// > add: fn(a: String, b: Int) {
/// > let assert [code] = string.to_utf_codepoints(a)
/// > let int_code = string.utf_codepoint_to_int(code)
/// > let new_int_code = int_code + b
/// > let assert Ok(new_code) = string.utf_codepoint(new_int_code)
/// > string.from_utf_codepoints([new_code])
/// > },
/// > compare: string.compare,
/// > )
///
/// > range("ab", "e", 1)
/// Error(Nil)
///
/// > let assert Ok(a_to_e) = range("a", "e", 1)
/// > a_to_e |> iterator.to_list
/// ["a", "b", "c", "d", "e"]
///
/// > let assert Ok(z_to_p) = range("z", "p", 1)
/// > z_to_p |> iterator.to_list
/// ["z", "y", "x", "w", "v", "u", "t", "s", "r", "q", "p"]
///
/// > let assert Ok(z_to_p) = range("z", "p", -2)
/// > z_to_p |> iterator.to_list
/// ["z", "x", "v", "t", "r", "p"]
///
/// > let assert Ok(z_to_p) = range("z", "p", 3)
/// > z_to_p |> iterator.to_list
/// ["z", "w", "t", "q"]
/// ```
///
///
/// ```gleam
/// > let range =
/// > create(
/// > validate: fn(_) { True },
/// > negate_step: fn(s) { -1.0 *. s },
/// > add: fn(a, b) { a +. b },
/// > compare: float.compare,
/// > )
///
/// > let assert Ok(weird_step_case) = range(1.25, 4.5, -0.5)
/// > weird_step_case |> iterator.to_list
/// [1.25, 1.75, 2.25, 2.75, 3.25, 3.75, 4.25]
///
/// > let assert Ok(single_item_case) = range(1.25, 1.25, -0.25)
/// > single_item_case |> iterator.to_list
/// [1.25]
///
/// > let assert Ok(zero_step_case) = range(2.5, 5.0, 0)
/// > zero_step_case |> iterator.to_list
/// [2.5]
/// ```
///
pub fn create(
validate validate: fn(item_type) -> Bool,
negate_step negate_step: fn(step_type) -> step_type,
add add: fn(item_type, step_type) -> item_type,
compare compare: fn(item_type, item_type) -> order.Order,
) -> fn(item_type, item_type, step_type) ->
Result(iterator.Iterator(item_type), Nil) {
let adjust_step = fn(a, b, s) -> Result(
option.Option(#(Direction, step_type)),
Nil,
) {
let negated_step = negate_step(s)
case
#(compare(a, b), compare(a, add(a, s)), compare(a, add(a, negated_step)))
{
#(order.Eq, _, _) -> Ok(option.None)
#(_, order.Eq, order.Eq) -> Ok(option.None)
#(order.Lt, order.Lt, _) -> Ok(option.Some(#(Forward, s)))
#(order.Lt, _, order.Lt) -> Ok(option.Some(#(Forward, negated_step)))
#(order.Lt, _, _) -> Error(Nil)
#(order.Gt, order.Gt, _) -> Ok(option.Some(#(Backward, s)))
#(order.Gt, _, order.Gt) -> Ok(option.Some(#(Backward, negated_step)))
#(order.Gt, _, _) -> Error(Nil)
}
}
fn(a: item_type, b: item_type, s: step_type) {
use <- bool.guard(!validate(a) || !validate(b), Error(Nil))
case adjust_step(a, b, s) {
Ok(option.Some(#(direction, step))) ->
Ok(
iterator.unfold(a, fn(current) {
case #(compare(current, b), direction) {
#(order.Gt, Forward) -> iterator.Done
#(order.Lt, Backward) -> iterator.Done
_ -> iterator.Next(current, add(current, step))
}
}),
)
Ok(option.None) -> Ok(iterator.once(fn() { a }))
Error(Nil) -> Error(Nil)
}
}
}
/// returns a function that can be used to create an infinite range
///
/// should be used carefully because careless use of infinite iterators could crash your app
///
/// ## Examples
///
/// ```gleam
/// > let range =
/// > create_infinite(
/// > validate: fn(a) { string.length(a) == 1 },
/// > add: fn(a: String, b: Int) {
/// > let assert [code] = string.to_utf_codepoints(a)
/// > let int_code = string.utf_codepoint_to_int(code)
/// > let new_int_code = int_code + b
/// > let assert Ok(new_code) = string.utf_codepoint(new_int_code)
/// > string.from_utf_codepoints([new_code])
/// > },
/// > compare: string.compare,
/// > )
///
/// > let assert Ok(from_a) = range("a", 1)
/// > from_a |> iterator.take(26) |> iterator.to_list
/// ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
/// "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
/// ```
pub fn create_infinite(
validate validate: fn(item_type) -> Bool,
add add: fn(item_type, step_type) -> item_type,
compare compare: fn(item_type, item_type) -> order.Order,
) -> fn(item_type, step_type) -> Result(iterator.Iterator(item_type), Nil) {
let is_step_zero = fn(a, s) -> Bool {
case compare(a, add(a, s)) {
order.Eq -> True
_ -> False
}
}
fn(a: item_type, s: step_type) {
use <- bool.guard(!validate(a), Error(Nil))
use <- bool.guard(
is_step_zero(a, s),
iterator.once(fn() { a })
|> Ok,
)
iterator.unfold(a, fn(current) { iterator.Next(current, add(current, s)) })
|> Ok
}
}