-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathiterators.lua
489 lines (428 loc) · 13.5 KB
/
iterators.lua
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
--[[ This file focuses on iterators, like pairs and ipairs. It defines functions similar to LINQ that return iterators. ]]--
-- Keep track of all iterator objects, but allow them to be GC'd
local iterators = setmetatable({}, {__mode = "k"})
local Iterator = {}
local function isIterator(o)
return type(o) == "function" or iterators[o]
end
local function asIterator(o)
return isIterator(o) and o or table.iterator(o)
end
--- Converts a table or iterator function into an iterator object.
-- An iterator object is just a wrapper for an iterator function with additional functionality attached to it.
-- @param target The object to iterate. If `target` is an iterator object or function, this function simply wraps it. If `target` is a table, this function creates an iterator object from it.
-- @param valuesOnly Whether to iterate through only the values of `target`. This parameter is only used if `target` is a table.
-- @param index Whether to use `ipairs` instead of `pairs` to create the iterator
function table.iterator(target, valuesOnly, index)
-- If onCall is a table to iterate, then convert it to a stateless iterator function
if not isIterator(target) then
local typ = type(target)
if typ == "table" then
local nxt, t, k, v
if (index) then
nxt, t, k, v = ipairs(target)
else
nxt, t, k, v = pairs(target)
end
target = function()
k, v = nxt(t, k, v)
if valuesOnly then
return v
end
return k, v
end
elseif typ == "string" then
local i = -1
local str = target
target = target:gmatch(".")
else
error("target must be iterable")
end
end
-- Create the iterator
local iter = {
target = target
}
iterators[iter] = true
return setmetatable(iter, {
__index = Iterator,
__call = target
})
end
--- Limits the output of the iterator
-- The output will be in the same order, but only values determined by `predicate` will be returned
-- @param predicate The function which determines whether a result should be returned by the iterator
function Iterator:where(predicate)
local this = self
return table.iterator(function()
local ret
repeat
ret = {this.target()}
until #ret == 0 or predicate(unpack(ret))
return unpack(ret)
end)
end
--- Modifies the results of the iterator
-- The values returned by `predicate` will be returned by the iterator instead
-- @param predicate The function which determines what will be output by the iterator
function Iterator:select(predicate)
local this = self
return table.iterator(function()
local ret = {this.target()}
if #ret > 0 then
return predicate(unpack(ret))
end
end)
end
--- Combines multiple iterators
-- This will not check that each iterator has the same number of results
function Iterator:concat(...)
local joined = {...}
local this = self
return table.iterator(function()
local ret = {this.target()}
while #ret == 0 and #joined > 0 do
this.target = asIterator(table.remove(joined, 1))
ret = {this.target()}
end
if #ret > 0 then
return unpack(ret)
end
end)
end
--- Creates a new iterator based on the results of two different iterators
-- The number of elements in the resulting iterator will be equal to the current number of elements or the number of elements in `second`, whichever is smaller
-- @param second The iterator to zip with
-- @param selector Optional. The function which determines what will be output by the iterator. Default is `function(...) return ... end`.
function Iterator:zip(second, selector)
second = asIterator(second)
selector = selector or function(...) return ... end
local this = self
return table.iterator(function()
local ret1 = {this.target()}
local ret2 = {second()}
if #ret1 > 0 and #ret2 > 0 then
for i, v in ipairs(ret2) do
table.insert(ret1, v)
end
return selector(unpack(ret1))
end
end)
end
--- Returns an iterator containing the elements in both this iterator and the second iterator
-- The elements in this iterator are returned
-- @param second The iterator to compare this to
-- @param comparer Optional. The function which determines which results are equivalent. Defualt compares each result from each iterator with each other
function Iterator:intersect(second, comparer)
second = asIterator(second)
-- Iterate `second` to be compared to later
local secondTable = {}
local r = {second()}
while #r > 0 do
table.insert(secondTable, r)
r = {second()}
end
local this = self
return table.iterator(function()
local r = {this.target()}
while #r > 0 do
local collision = false
-- Compare to each element in `second`
for _, elem in ipairs(secondTable) do
if comparer then
local t = {unpack(r)}
for i, v in ipairs(elem) do
table.insert(t, v)
end
if comparer(unpack(t)) then
collision = true
break
end
else
collision = true
for i, v in ipairs(r) do
if elem[i] ~= v then
collision = false
break
end
end
if collision then
break
end
end
end
if collision then
return unpack(r)
end
r = {this.target()}
end
return nil
end)
end
--- Returns an iterator containing the elements in this iterator and not the second iterator
-- The elements in this iterator are returned
-- @param second The iterator to compare this to
-- @param comparer Optional. The function which determines which results are equivalent. Defualt compares each result from each iterator with each other
function Iterator:except(second, comparer)
second = asIterator(second)
-- Iterate `second` to be compared to later
local secondTable = {}
local r = {second()}
while #r > 0 do
table.insert(secondTable, r)
r = {second()}
end
local this = self
return table.iterator(function()
local r = {this.target()}
while #r > 0 do
local collision = false
-- Compare to each element in `second`
for _, elem in ipairs(secondTable) do
if comparer then
local t = {unpack(r)}
for i, v in ipairs(elem) do
table.insert(t, v)
end
if comparer(unpack(t)) then
collision = true
break
end
else
collision = true
for i, v in ipairs(r) do
if elem[i] ~= v then
collision = false
break
end
end
if collision then
break
end
end
end
if not collision then
return unpack(r)
end
r = {this.target()}
end
return nil
end)
end
--- Returns an iterator containing only the unique results from this iterator
-- The first instances of the distinct results are returned
-- @param comparer Optional. The function which determines which results are equivalent. Defualt compares each element of each result with each other
function Iterator:distinct(comparer)
local checked = {}
local this = self
return table.iterator(function()
local r = {this.target()}
while #r > 0 do
local collision = false
-- Compare to each element in `second`
for _, elem in ipairs(secondTable) do
if comparer then
local t = {unpack(r)}
for i, v in ipairs(elem) do
table.insert(t, v)
end
if comparer(unpack(t)) then
collision = true
break
end
else
collision = true
for i, v in ipairs(r) do
if elem[i] ~= v then
collision = false
break
end
end
if collision then
break
end
end
end
if not collision then
table.insert(checked, r)
return unpack(r)
end
r = {this.target()}
end
return nil
end)
end
--- Skips the first `n` results of this iterator
-- Does not create a new iterator
-- @param n The number of results to skip
function Iterator:skip(n)
while n > 0 and self.target() do
n = n - 1
end
return self
end
--- Limits this iterator to the first `n` results
-- Creates a new iterator from this one. It is not cloned.
-- @param n The number of results to take
function Iterator:take(n)
local this = self
return table.iterator(function()
if n > 0 then
n = n - 1
return this.target()
end
end)
end
--- Reverses the results from this iterator
-- Iterates the whole iterator
function Iterator:reverse()
local buffer = self:totable()
return table.iterator(function()
if #buffer == 0 then
return
end
return unpack(table.remove(buffer))
end)
end
--- Sorts the elements in this iterator
-- Iterates the whole iterator
-- @param comparer The comparison function passed into `table.sort`
function Iterator:sort(comparer)
local buffer = self:totable()
table.sort(buffer, function(a, b)
local t = {unpack(a)}
for i, v in ipairs(b) do
table.insert(t, v)
end
return comparer(unpack(t))
end)
return table.iterator(buffer, true, true):unpack()
end
--- Returns whether the iterator will return anything, optionally filtered by a predicate
-- If `predicate` is passed in, this will check if it returns true for any elements being iterated
-- @param predicate Optional. Determines which results it should be checking for
function Iterator:any(predicate)
local ret = {self.target()}
local buffer = {}
while #ret > 0 do
table.insert(buffer, ret)
if not predicate or predicate(unpack(ret)) then
self.target = table.iterator(buffer, true, true).select(function(t) return unpack(t) end).concat(self.target)
return true
end
ret = {self.target()}
end
self.target = table.iterator(buffer, true, true)
return false
end
--- Returns whether all results from this iterator meet the given `predicate`
-- This will create a new iterator that is a clone of this one
-- @param predicate The function to match each result against
function Iterator:all(predicate)
local ret = {self.target()}
local buffer = {}
while #ret > 0 do
table.insert(buffer, ret)
if not predicate(unpack(ret)) then
self.target = table.iterator(buffer, true, true).select(function(t) return unpack(t) end).concat(self.target)
return false
end
ret = {self.target()}
end
self.target = table.iterator(buffer, true, true)
return true
end
--- Returns the first result from the iterator, optionally filtered by a predicate
-- If `predicate` is passed in, will return the first result from the iterator that the predicate matches
-- @param predicate Optional. The function to match each result against
function Iterator:first(predicate)
local ret = {self.target()}
local buffer = {}
while #ret > 0 do
table.insert(buffer, ret)
if not predicate or predicate(unpack(ret)) then
self.target = table.iterator(buffer, true, true).select(function(t) return unpack(t) end).concat(self.target)
return unpack(ret)
end
ret = {self.target()}
end
self.target = table.iterator(buffer, true, true)
return false
end
function Iterator:last(predicate)
local ret = {self.target()}
local last = nil
local buffer = {}
while #ret > 0 do
table.insert(buffer, ret)
if not predicate or predicate(unpack(ret)) then
self.target = table.iterator(buffer, true, true).select(function(t) return unpack(t) end).concat(self.target)
last = ret
end
ret = {self.target()}
end
self.target = table.iterator(buffer, true, true)
if last then
return unpack(last)
end
end
function Iterator:aggregate(seed, func, resultSelector)
if type(seed) == "function" then
resultSelector = func
func = seed
seed = nil
end
local ret = {self.target()}
while #ret > 0 do
seed = func(seed, unpack(ret))
ret = {self.target()}
end
return resultSelector and resultSelector(seed) or seed
end
function Iterator:unpack()
local this = self
return table.iterator(function()
local ret = {this.target()}
if #ret > 0 then
return unpack(ret[1])
end
end)
end
function Iterator:totable(keySelector, valueSelector)
local t = {}
local ret = {self.target()}
if keySelector then
-- Use keySelector to select keys
while #ret > 0 do
t[keySelector(unpack(ret))] = valueSelector and valueSelector(unpack(ret)) or ret[1]
ret = {self.target()}
end
else
-- Keys will be an incrementing integer starting at 1.
local i = 1
while #ret > 0 do
t[i] = valueSelector and valueSelector(unpack(ret)) or ret
ret = {self.target()}
i = i + 1
end
--end
end
return t
end
--- Returns an iterator containing number values.
-- If no `start` is defined, this function will start at `1`.
-- @param start Optional. What number to start at. Default value is `1`. (Inclusive)
-- @param finish What number to end at. (Inclusive)
-- @param step Optional. Difference between each element. Default value is `1`.
function table.range(start, finish, step)
step = step or 1
if not finish then
finish = start
start = 1
end
local i = start - step
return table.iterator(function()
i = i + step
return i <= finish and i or nil
end)
end