-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathfunctional.lua
More file actions
1110 lines (1022 loc) · 29.6 KB
/
Copy pathfunctional.lua
File metadata and controls
1110 lines (1022 loc) · 29.6 KB
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
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---@author BakersDozenBagels <business@gdane.net>
---@copyright (c) 2025 BakersDozenBagels
---@license GPL-3.0
---@version 2.0.4
local f = {}
---@deprecated
f.lazy = {} -- Lazily-evaluated versions of the functions. The return values use metatables and so should not be serialized.
F = f -- export as global; change this line as desired
-- Polyfill Lua 5.2 behavior for `pairs` and `ipairs`.
--- @alias pairs fun(table: table<any, any>, index?: any): any, any
local raw_pairs = pairs
pairs = function(t)
local metatable = getmetatable(t)
if metatable and metatable.__pairs then
return metatable.__pairs(t)
end
return raw_pairs(t)
end
local raw_ipairs = ipairs
ipairs = function(t)
local metatable = getmetatable(t)
if metatable and metatable.__ipairs then
return metatable.__ipairs(t)
end
return raw_ipairs(t)
end
-- "Polyfill" Lua 5.2 behavior for `#obj`.
--- Returns the number of keys in `obj`.
---@param obj table
---@return integer
---@nodiscard
function f.count(obj)
local metatable = getmetatable(obj)
if metatable and metatable.__len then
return metatable.__len(obj)
end
return #obj
end
--- Generates a table like {4, 5, 6, 7}.
---@param min? integer The inclusive lower bound of the range. Defaults to `1`.
---@param max? integer The inclusive upper bound of the range. Defaults to `1`.
---@param step? integer The step between elements. Defaults to `1`.
---@return table
---@nodiscard
function f.range(min, max, step)
local ret = {}
local ix = 1
for i = min, max, step do
ret[ix] = i
ix = ix + 1
end
return ret
end
--- Performs a functional mapping.
---@param obj table The table to map over.
---@param func? (fun(value, key): any) The mapping function. Defaults to `f.id`.
---@param f_pairs? pairs The method to iterate over `obj`. Defaults to `pairs`.
---@return table
---@nodiscard
function f.map(obj, func, f_pairs)
func = func or f.id
f_pairs = f_pairs or pairs
local ret = {}
for k, v in f_pairs(obj) do
ret[k] = func(v, k)
end
return ret
end
--- Performs a functional reduction.
---@param obj table The table to reduce.
---@param seed? any The initial value for the accumulator. By default, uses the first value in `obj` (and skips reducing that index).
---@param func (fun(accumulator, value, key): any) The reduction function.
---@param f_ipairs? (fun(table): function, table, any) The method to iterate over `obj`. Defaults to `ipairs`.
---@return any
---@nodiscard
function f.reduce(obj, seed, func, f_ipairs)
f_ipairs = f_ipairs or ipairs
local ret = seed
local it, table, first = f_ipairs(obj)
if not ret then
first, ret = it(table)
end
for k, v in it, table, first do
ret = func(ret, v, k)
end
return ret
end
--- Returns `true` if and only if `func(obj[k])` is truthy for any `k`. Otherwise, returns false.
---@param obj table The table to reduce.
---@param func? (fun(value, key): boolean) The predicate function.
---@param f_pairs? (fun(table): function, table, any) The method to iterate over `obj`. Defaults to `pairs`.
---@return boolean
---@nodiscard
function f.any(obj, func, f_pairs)
f_pairs = f_pairs or pairs
for k, v in f_pairs(obj) do
if not func or func(v, k) then
return true
end
end
return false
end
--- Returns `true` if and only if `func(obj[k])` is truthy for all `k`. Otherwise, returns false.
---@param obj (table) The table to reduce.
---@param func (fun(value, key): boolean) The predicate function.
---@param f_pairs? (fun(table): function, table, any) The method to iterate over `obj`. Defaults to `pairs`.
---@return boolean
---@nodiscard
function f.all(obj, func, f_pairs)
f_pairs = f_pairs or pairs
for k, v in f_pairs(obj) do
if not func(v, k) then
return false
end
end
return true
end
--- Returns `true` if and only if `func(obj[k])` is falsy for all `k`. Otherwise, returns false.
---@param obj table The table to reduce.
---@param func? fun(value, key): boolean) The predicate function.
---@param f_pairs? fun(table): function, table, any) The method to iterate over `obj`. Defaults to `pairs`.
---@return boolean
---@nodiscard
function f.none(obj, func, f_pairs)
f_pairs = f_pairs or pairs
for k, v in f_pairs(obj) do
if not func or func(v, k) then
return false
end
end
return true
end
--- Returns a new table with only the elements which pass a test.
---@param obj table The table to filter.
---@param func fun(value, key): boolean) The predicate function.
---@param f_pairs? fun(table): function, table, any) The method to iterate over `obj`. Defaults to `pairs`.
---@return table
---@nodiscard
function f.filter(obj, func, f_pairs)
f_pairs = f_pairs or pairs
local ret = {}
for k, v in f_pairs(obj) do
if func(v, k) then
ret[k] = v
end
end
return ret
end
--- Returns a new table with only the elements in the specified inclusive range. Elements are renumbered.
---@param obj table The table to filter.
---@param start? integer The inclusive minimum index. Defaults to `1`.
---@param _end? integer The inclusive maximum index. Defaults to infinity.
---@param f_ipairs? (fun(table): function, table, any) The method to iterate over `obj`. Defaults to `ipairs`.
---@return table
---@nodiscard
function f.slice(obj, start, _end, f_ipairs)
f_ipairs = f_ipairs or ipairs
start = start or 1
local ret = {}
local i = 1
for k, v in f_ipairs(obj) do
if i >= start and (not _end or i <= _end) then
ret[k] = v
end
end
return ret
end
--- Function that always performs no operation (no-op).
function f.noop() end
--- Identity function over any number of inputs.
---@generic T
---@param ... T
---@return T
function f.id(...)
return ...
end
--- Function that always returns false.
---@return false
function f.fals()
return false
end
--- Function that always returns true.
---@return true
function f.tru()
return true
end
--- Creates a function that always returns the same values.
---@generic T
---@param ... T
---@return fun(): T
function f.const(...)
local args = { ... }
return function()
return unpack(args)
end
end
--- Runs a function on every value in the table.
---@param obj table The table to use.
---@param func? (fun(value, key): any) The function to run. Defaults to `f.id`.
---@param f_pairs? pairs The method to iterate over `obj`. Defaults to `pairs`.
---@return table The original table unchanged.
function f.foreach(obj, func, f_pairs)
func = func or f.id
f_pairs = f_pairs or pairs
for k, v in f_pairs(obj) do
func(v, k)
end
return obj
end
--- Merges two tables.
---@param a table The first table.
---@param b table The second table.
---@param f_pairs_a? pairs The method to iterate over `a`. Defaults to `pairs`.
---@param f_pairs_b? pairs The method to iterate over `b`. Defaults to `pairs`.
---@return table
---@nodiscard
function f.merge(a, b, f_pairs_a, f_pairs_b)
f_pairs_a = f_pairs_a or pairs
f_pairs_b = f_pairs_b or pairs
local ret = {}
for k, v in f_pairs_a(a) do
ret[k] = v
end
for k, v in f_pairs_b(b) do
ret[k] = v
end
return ret
end
--- Concatenates two tables into a numerically-indexed table.
---@param a table The first table.
---@param b table The second table.
---@param f_ipairs_a? pairs The method to iterate over `a`. Defaults to `ipairs`.
---@param f_ipairs_b? pairs The method to iterate over `b`. Defaults to `ipairs`.
---@return table
---@nodiscard
function f.concat(a, b, f_ipairs_a, f_ipairs_b)
f_ipairs_a = f_ipairs_a or ipairs
f_ipairs_b = f_ipairs_b or ipairs
local ret = {}
local i = 1
for k, v in f_ipairs_a(a) do
ret[i] = v
i = i + 1
end
for k, v in f_ipairs_b(b) do
ret[i] = v
i = i + 1
end
return ret
end
--- Returns the keys of a table indexed numerically.
---@param table table The table.
---@param f_pairs? pairs The method to iterate over `table`. Defaults to `pairs`.
---@return table
---@nodiscard
function f.keys(table, f_pairs)
f_pairs = f_pairs or pairs
local ret = {}
for k in f_pairs(table) do
ret[#ret + 1] = k
end
return ret
end
--- Returns the values of a table indexed numerically.
---@param table table The table.
---@param f_pairs? pairs The method to iterate over `table`. Defaults to `pairs`.
---@return table
---@nodiscard
function f.values(table, f_pairs)
f_pairs = f_pairs or pairs
local ret = {}
for _, v in f_pairs(table) do
ret[#ret + 1] = v
end
return ret
end
--- Returns the key-value pairs of a table indexed numerically.
--- Each key-value pair is represented as `{ key, value, key=key, value=value }`.
---@param table table The table.
---@param f_pairs? pairs The method to iterate over `table`. Defaults to `pairs`.
---@return table
---@nodiscard
function f.entries(table, f_pairs)
f_pairs = f_pairs or pairs
local ret = {}
for k, v in f_pairs(table) do
ret[#ret + 1] = {
k,
v,
k = k,
v = v,
key = k,
value = v
}
end
return ret
end
--- Generates a table like {4, 5, 6, 7}.
---@param min? integer The inclusive lower bound of the range. Defaults to `1`.
---@param max? integer The inclusive upper bound of the range. Defaults to no upper bound.
---@param step? integer The step between elements. Defaults to `1`.
---@return table
---@nodiscard
function f.lazy.range(min, max, step)
min = min or 1
step = step or 1
local mt
mt = {
__ipairs = function(self)
return mt.__ipairs_next, self, 0
end,
__ipairs_next = function(self, i)
local v = min + i * step
i = i + 1
if v >= min and (not max or v <= max) then
return i, v
end
end,
__index = function(self, i)
local v = min + (i - 1) * step
return v >= min and (not max or v <= max) and v or nil
end,
__newindex = function(self, nk, nv)
mt.__eager(self)
self[nk] = nv
end,
__eager = function(self)
setmetatable(self, nil)
for k, v in mt.__ipairs(self) do
self[k] = v
end
end,
__len = function(self)
return math.ceil((max - min + 1) / step)
end
}
mt.__pairs = mt.__ipairs
return setmetatable({}, mt)
end
--- Performs a functional mapping.
---@param obj table The table to map over.
---@param func? (fun(value, key): any) The mapping function. Defaults to `f.id`.
---@param f_pairs? pairs The method to iterate over `obj`. Defaults to `pairs`.
---@return table
---@nodiscard
function f.lazy.map(obj, func, f_pairs)
f_pairs = f_pairs or pairs
func = func or f.id
local mt, f_next
mt = {
__pairs = function(self)
return mt.__pairs_next, self, nil
end,
__pairs_next = function(self, i)
if f_next == nil then
local _
f_next, _, i = f_pairs(obj)
end
local k, v = f_next(obj, i)
if k ~= nil then
return k, func(v, k)
end
end,
__index = function(self, i)
return func(obj[i], i)
end,
__newindex = function(self, nk, nv)
mt.__eager(self)
self[nk] = nv
end,
__eager = function(self)
setmetatable(self, nil)
for k, v in mt.__pairs(self) do
self[k] = v
end
end,
__len = function(self)
return #obj
end
}
return setmetatable({}, mt)
end
f.lazy.reduce = f.reduce
f.lazy.any = f.any
f.lazy.all = f.all
f.lazy.none = f.none
--- Returns a new table with only the elements which pass a test.
---@param obj table The table to filter.
---@param func (fun(value, key): boolean) The predicate function.
---@param f_pairs? (fun(table): function, table, any) The method to iterate over `obj`. Defaults to `pairs`.
---@return table
---@nodiscard
function f.lazy.filter(obj, func, f_pairs)
f_pairs = f_pairs or pairs
local mt, f_next
mt = {
__pairs = function(self)
return mt.__pairs_next, self, nil
end,
__pairs_next = function(self, i)
if f_next == nil then
local _
f_next, _, i = f_pairs(obj)
end
local k, v = f_next(obj, i)
if k ~= nil then
if not func(v, k) then
return mt.__pairs_next(self, k)
else
return k, v
end
end
end,
__index = function(self, i)
return func(obj[i], i) and obj[i] or nil
end,
__newindex = function(self, nk, nv)
mt.__eager(self)
self[nk] = nv
end,
__eager = function(self)
setmetatable(self, nil)
for k, v in mt.__pairs(self) do
self[k] = v
end
end,
__len = function(self)
mt.__eager(self)
return #self
end
}
return setmetatable({}, mt)
end
--- Returns a new table with only the elements in the specified inclusive range. Elements are renumbered.
---@param obj table The table to filter.
---@param start? integer The inclusive minimum index. Defaults to `1`.
---@param _end? integer The inclusive maximum index. Defaults to infinity.
---@param f_ipairs? (fun(table): function, table, any) The method to iterate over `obj`. Defaults to `ipairs`.
---@return table
---@nodiscard
function f.lazy.slice(obj, start, _end, f_ipairs)
f_ipairs = f_ipairs or pairs
start = start or 1
if _end then
_end = _end - start + 1
end
local mt
mt = {
__ipairs = function(self)
local ix
local f_next, f_obj
local function next(self, i)
if f_next == nil then
f_next, f_obj, ix = f_ipairs(obj)
for i = 2, start do
ix = f_next(f_obj, ix)
end
end
i = i + 1
if i <= _end then
local v
ix, v = f_next(f_obj, ix)
return i, v
end
end
return next, self, 0
end,
__index = function(self, i)
if i >= 1 and (not _end or i <= _end) then
return obj[start + i - 1]
end
end,
__newindex = function(self, nk, nv)
mt.__eager(self)
self[nk] = nv
end,
__eager = function(self)
setmetatable(self, nil)
for k, v in mt.__pairs(self) do
self[k] = v
end
end,
__len = function(self)
return math.min(f.lazy.count(obj), _end or math.huge)
end
}
mt.__pairs = mt.__ipairs
return setmetatable({}, mt)
end
f.lazy.id = f.id
f.lazy.count = f.count
f.lazy.index = f.index
--- Eagerly runs a function on every value in the table.
---@param obj table The table to use.
---@param func? (fun(value, key): any) The function to run. Defaults to `f.id`.
---@param f_pairs? pairs The method to iterate over `obj`. Defaults to `pairs`.
---@return table obj The original table unchanged.
---@nodiscard
function f.lazy.foreach(obj, func, f_pairs)
func = func or f.id
f_pairs = f_pairs or pairs
for k, v in f_pairs(obj) do
func(v, k)
end
return obj
end
--- Merges two tables.
---@param a (table) The first table.
---@param b (table) The second table.
---@param f_pairs_a? pairs The method to iterate over `a`. Defaults to `pairs`.
---@param f_pairs_b? pairs The method to iterate over `b`. Defaults to `pairs`.
function f.lazy.merge(a, b, f_pairs_a, f_pairs_b)
error("Not implemented")
end
--- Concatenates two tables into a numerically-indexed table.
---@param a (table) The first table.
---@param b (table) The second table.
---@param f_ipairs_a? pairs The method to iterate over `a`. Defaults to `ipairs`.
---@param f_ipairs_b? pairs The method to iterate over `b`. Defaults to `ipairs`.
function f.lazy.concat(a, b, f_ipairs_a, f_ipairs_b)
error("Not implemented")
end
--- Returns the keys of a table indexed numerically.
---@param table (table) The table.
---@param f_pairs? pairs The method to iterate over `table`. Defaults to `pairs`.
function f.lazy.keys(table, f_pairs)
error("Not implemented")
end
--- Returns the values of a table indexed numerically.
---@param table (table) The table.
---@param f_pairs? pairs The method to iterate over `table`. Defaults to `pairs`.
function f.lazy.values(table, f_pairs)
error("Not implemented")
end
--- Returns the key-value pairs of a table indexed numerically.
--- Each key-value pair is represented as `{ [1]=key, [2]=value, k=key, v=value }`.
---@param table (table) The table.
---@param f_pairs? pairs The method to iterate over `table`. Defaults to `pairs`.
function f.lazy.entries(table, f_pairs)
error("Not implemented")
end
--- Eagerly evaluates a lazy sequence.
--- The sequence will no longer use a metatable.
---@param obj table
function f.lazy.to_eager(obj)
local metatable = getmetatable(obj)
if metatable and metatable.__eager then
metatable.__eager(obj)
end
return obj
end
---@generic K, V
---@alias Next fun(self):K?, V?
---@class Query<K, V>: metatable
f._proto = {}
if false then
--- Gets the next pair from this collection.
---@type Next
function f._proto.next()
return 1, 2
end
--- Indicates whether this collection is numerically-indexed.
---@type boolean
f._proto.numeric = false
end
--- Maps elements from this collection to new ones.
---@generic K, V, U
---@param func fun(value: V, key: K): U
---@return Query<K, U>
---@nodiscard
function f._proto:map(func)
return f._wrap(function()
local k, v = self:next()
if k == nil then
return k, v
end
return k, func(v, k)
end, self.numeric)
end
--- Maps elements from this collection to sequences of new ones.
---@generic K, V, U
---@param func fun(value: V, key: K): Query<K, U>
---@return Query<K, U>
---@nodiscard
function f._proto:flatmap(func)
return self
:map(func)
:map(function(x) return (x.pairs and f.bind(x.pairs, x) or (self.numeric and f.ipairs or f.pairs))(x) end)
:reduce(f.concat)
end
--- Performs a cross join on two queries.
---@generic K1, V1, K2, V2, U
---@param other Query<K2, V2>
---@param func fun(v1: V1, v2: V2, k1: K1, k2: K2): U
---@return Query<integer, U>
---@nodiscard
function f._proto:join(other, func)
local ret = {}
for k1, v1 in self:pairs() do
for k2, v2 in other:pairs() do
ret[#ret + 1] = func(v1, v2, k1, k2)
end
end
return f.ipairs(ret)
end
--- Zips two queries together.
---@generic V1, V2, U
---@param other Query<integer, V2>
---@param func? fun(v1: V1, v2: V2, k: integer): U Default to `function(...) return {...} end`
---@return Query<integer, U>
---@nodiscard
function f._proto:zip(other, func)
if not self.numeric or not other.numeric then error("Zipping is only supported on two numerically-indexed queries", 2) end
func = func or function(...) return { ... } end
return f._wrap(function()
local k1, v1 = self:next()
local k2, v2 = other:next()
if k1 == nil or k2 == nil then
return
end
if k1 ~= k2 then error("Keys are mismatched") end
return k1, func(v1, v2, k1)
end, true)
end
--- Reduces the collection to a single element. The seed is optional.
---@generic K, V, A
---@param seed A|fun(a: A, b: V, k: K): A
---@param func? fun(a: A, b: V, k: K): A
---@return A
---@nodiscard
function f._proto:reduce(seed, func)
local k, v
if type(func) == 'nil' then
func = seed
k, seed = self:next()
if k == nil then
return seed
end
end
while true do
k, v = self:next()
if k == nil then
return seed
end
seed = func(seed, v, k)
end
end
--- Returns the first truthy element, or false if none exist.
---@param func? (fun(value, key): boolean) The predicate function.
---@return boolean
---@nodiscard
function f._proto:any(func)
for k, v in self:pairs() do
if func then
if func(v, k) then
return v
end
elseif v then
return v
end
end
return false
end
--- Returns the first falsy element, or true if none exist.
---@param func? (fun(value, key): boolean) The predicate function.
---@return boolean
---@nodiscard
function f._proto:all(func)
for k, v in self:pairs() do
if func then
if not func(v, k) then
return v
end
elseif not v then
return v
end
end
return true
end
--- Count the number of entries in the sequence, optionally filtered by a function.
---@generic K, V
---@param func? fun(v: V, k: K):boolean
---@return integer
---@nodiscard
function f._proto:count(func)
local o = self
if func then o = o:filter(func) end
return o:reduce(0, function(x) return x + 1 end)
end
--- Filters elements in the collection to only those that match a given predicate.
--- This will renumber the keys of a numerically-indexed collection.
---@generic K, V
---@param func fun(value: V, key: K): boolean
---@return Query<K, V>
---@nodiscard
function f._proto:filter(func)
if not func then
error("Expected a filter function", 2)
end
local ix = 0
return f._wrap(function()
local k, v
repeat
k, v = self:next()
until k == nil or func(v, k)
if k == nil or not self.numeric then
return k, v
end
ix = ix + 1
return ix, v
end, self.numeric)
end
--- Limits the query to the first n elements of the numerically-indexed collection.
---@param n integer
---@return Query
---@nodiscard
function f._proto:take(n)
if not n or n < 0 then
error("Expected non-negative number", 2)
end
if not self.numeric then
error("Take is only supported for numerically-indexed tables", 2)
end
return f._wrap(function()
n = n - 1
if n >= 0 then
return self:next()
end
end, true)
end
--- Skips the first n elements of the numerically-indexed collection.
---@param n integer
---@return Query
---@nodiscard
function f._proto:skip(n)
if not self.numeric then
error("Skip is only supported for numerically-indexed tables", 2)
end
for i = 1, n - 1 do
self:next()
end
local ix = 0
return f._wrap(function()
ix = ix + 1
local k, v = self:next()
if k ~= nil then
return ix, v
end
end, true)
end
--- Runs a function on every element in the collection in encounter order.
---@generic K, V
---@param func fun(value: V, key: K)
---@return Query<K, V>
function f._proto:foreach(func)
for k, v in self:pairs() do
func(v, k)
end
return self
end
--- Appends another query to this one.
---@see f.q_concat
---@param other Query
---@return Query
---@nodiscard
function f._proto:prepend(other)
return f.q_concat(other, self)
end
--- Appends this query to another.
---@see f.q_concat
---@param other Query
---@return Query
---@nodiscard
function f._proto:append(other)
return f.q_concat(self, other)
end
--- Concatenates two queries.
--- If both are numerically-indexed, the result is as well, and keys will be renumbered.
--- Otherwise, the result will not be numerically-indexed, and keys will not be numbered.
---@param a Query
---@param b Query
---@return Query
---@nodiscard
function f.q_concat(a, b)
local ix, swap = 0, false
return f._wrap(function()
ix = ix + 1
local k, v = (swap and b or a):next()
if k == nil and not swap then
swap = true
k, v = b:next()
end
if k ~= nil then
return a.numeric and b.numeric and ix or k, v
end
end, a.numeric and b.numeric)
end
--- Returns a numerically-indexed query of this non-numerically-indexed query's keys.
---@generic K
---@return Query<integer, K>
---@nodiscard
function f._proto:keys()
if self.numeric then
error("Keys is not supported on numerically-indexed tables", 2)
end
local ix = 0
return f._wrap(function()
local k = self:next()
if k ~= nil then
ix = ix + 1
return ix, k
end
end, true)
end
--- Returns a numerically-indexed query of this non-numerically-indexed query's values.
---@generic V
---@return Query<integer, V>
---@nodiscard
function f._proto:values()
if self.numeric then
error("Values is not supported on numerically-indexed tables", 2)
end
local ix = 0
return f._wrap(function()
local k, v = self:next()
if k ~= nil then
ix = ix + 1
return ix, v
end
end, true)
end
--- Returns a numerically-indexed query of this query's key-value pairs.
---@generic K, V
---@return Query<integer, {[1]:K, [2]:V, k: K, v: V}>
---@nodiscard
function f._proto:entries()
local ix = 0
return f._wrap(function()
local k, v = self:next()
if k ~= nil then
ix = ix + 1
return ix, {
k,
v,
k = k,
v = v
}
end
end, true)
end
--- Returns an identical query treated as non-numerically-indexed.
---@return Query
---@nodiscard
function f._proto:unordered()
if not self.numeric then
error("Unordered is only supported on numerically-indexed tables", 2)
end
return f._wrap(self.next, false)
end
--- Returns an identical query treated as numerically-indexed.
---@return Query
---@nodiscard
function f._proto:ordered()
if self.numeric then
error("Ordered is not supported on numerically-indexed tables", 2)
end
return f._wrap(self.next, true)
end
--- Brings this query into the supplied order.
---@generic K, V
---@param func fun(v1: V, v2: V, k1: K, k2: K): boolean Less than function
---@return Query<K, V>
---@nodiscard
function f._proto:sorted(func)
local q = self.numeric and self:unordered() or self
local t = q:entries():into()
table.sort(t, function(a, b) return func(a.v, b.v, a.k, b.k) end)
local ix = 0
return f._wrap(function()
ix = ix + 1
local v = t[ix]
if v then
return v.k, v.v
end
end, true)
end
--- Joins the strings together with an optional separator.
---@param separator? string
---@return string
---@nodiscard
function f._proto:conjoin(separator)
separator = separator or ''
---@type string
return self:reduce(function(a, b) return tostring(a) .. separator .. tostring(b) end)
end
--- Converts the query to a string. Helpful for debugging.
---@return string
---@nodiscard
function f._proto:tostring()
return '{ ' .. self:map(function(v, k)
return '[' .. tostring(k) .. '] = ' .. tostring(v)
end):reduce(function(a, b)
return a .. ', ' .. b
end) .. ' }'
end
--- Converts the query to a table.
---@return table
---@nodiscard
function f._proto:into()
local ret = {}
for k, v in self.next do
ret[k] = v
end
return ret
end
--- Gets the iterator for this query.
---@generic K, V
---@return fun():K, V
---@nodiscard
function f._proto:pairs()
return self.next
end