-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathcollections.lua
More file actions
1278 lines (1109 loc) · 33.1 KB
/
collections.lua
File metadata and controls
1278 lines (1109 loc) · 33.1 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
-- collections.lua - A robust collection class based on Laravel collections
--
-- @module collections.lua
-- @alias Collection
-- @url https://github.com/ImLiam/Lua-Collections
local Collection = {}
Collection.version = '0.2.0'
--- Alias for the Collection:new() method
function collect(tbl)
return Collection:new(tbl)
end
-----[[ COLLECTION METHODS ]]-----
--- Returns all elements from a collection as a table
function Collection:all()
local tbl = {}
for key, value in pairs(self.table) do
tbl[key] = value
end
return tbl
end
--- Adds an item to the end of a collection
function Collection:append(value)
table.insert(self.table, value)
return self
end
--- Returns the average value of a list or given key
function Collection:average(key)
local count = self:count()
if count > 0 then
return self:sum(key) / count
end
end
--- Breaks the collection into multiple smaller collections of a given size
function Collection:chunk(count)
local chunks = self:new()
local currentChunk = {}
if count <= 0 then
return Collection:new({ {} })
end
for key, value in pairs(self.table) do
table.insert(currentChunk, value)
if #currentChunk == count then
chunks:push(currentChunk)
currentChunk = {}
end
end
if #currentChunk > 0 then
chunks:push(currentChunk)
end
return chunks
end
--- Returns a copy of the collection
function Collection:clone()
local cloned = {}
for key, value in pairs(self.table) do
cloned[key] = value
end
return self:new(cloned)
end
--- Collapses a collection of tables into a single, flat collection
function Collection:collapse()
local collapsed = self:new()
for key, value in pairs(self.table) do
for innerKey, innerValue in pairs(value) do
collapsed:push(innerValue)
end
end
return collapsed
end
--- Combines the keys of the collection with the values of another table
function Collection:combine(values)
local combined = self:new()
for key, value in pairs(values) do
if self.table[key] then
combined:set(self.table[key], value)
end
end
return combined
end
--- Determines whether the collection contains a given item
function Collection:contains(containValue, recursive)
local function checkContains(key, value)
if type(containValue) == 'function' then
result = containValue(key, value)
if result then
return true
end
else
if value == containValue then
return true
end
end
return false
end
for key, value in pairs(self.table) do
local result
if type(value) == 'table' and recursive then
for innerKey, innerValue in pairs(value) do
if checkContains(innerKey, innerValue) then
return true
end
end
else
if checkContains(key, value) then
return true
end
end
end
return false
end
--- Turns an associative table into an indexed one, removing string keys
function Collection:convertToIndexed()
local notAssociative = self:new()
for key, value in pairs(self.table) do
notAssociative:push(value)
end
return notAssociative
end
--- Returns the total number of items in the collection
function Collection:count()
local i = 0
for key, value in pairs(self.table) do
i = i + 1
end
return i
end
--- Deals the collection into a number of groups in order, one at a time
function Collection:deal(hands)
local splitted = self:times(hands, function() return {} end)
local currentSection = 1
for key, value in pairs(self.table) do
table.insert(splitted.table[currentSection], value)
currentSection = currentSection + 1
if currentSection > #splitted.table then
currentSection = 1
end
end
return splitted
end
--- Compares a collection against another table based on its values
--- Returns the values in the original collection that are not present in the given table
function Collection:diff(difference)
local differenceList = {}
for key, value in pairs(difference) do
differenceList[value] = true
end
local finalDifferences = self:new()
for key, value in pairs(self.table) do
if not differenceList[value] then
finalDifferences:push(value)
end
end
return finalDifferences
end
--- Compares the collection against another table based on its keys
--- Returns the key / value pairs in the original collection that are not present in the table
function Collection:diffKeys(difference)
local differenceList = {}
for key, value in pairs(difference) do
differenceList[key] = true
end
local finalDifferences = self:new()
for key, value in pairs(self.table) do
if not differenceList[key] then
finalDifferences:set(key, value)
end
end
return finalDifferences
end
--- Iterates over the items in the collection and passes each to a callback
function Collection:each(callback)
for key, value in pairs(self.table) do
if callback(key, value) == false then
break
end
end
return self
end
--- Iterates over the numerically indexed items in the collection and passes each to a callback
function Collection:eachi(callback)
for key, value in ipairs(self.table) do
if callback(key, value) == false then
break
end
end
return self
end
--- Compares a table with the internal table of the collection
function Collection:equals(tbl, ignoreMetatables, tbl2)
tbl2 = tbl2 or self.table
if tbl == tbl2 then return true end
local tblType = type(tbl)
local tbl2Type = type(tbl2)
if tblType ~= tbl2Type then return false end
if tblType ~= 'table' then return false end
if not ignoreMetatables then
local mt1 = getmetatable(tbl)
if mt1 and mt1.__eq then
return tbl == tbl2
end
end
local keySet = {}
for key1, value1 in pairs(tbl) do
local value2 = tbl2[key1]
if value2 == nil or self:equals(value1, ignoreMetatables, value2) == false then
return false
end
keySet[key1] = true
end
for key2, _ in pairs(tbl2) do
if not keySet[key2] then return false end
end
return true
end
--- Verify that all elements of the collection pass a truth test
function Collection:every(callback)
for key, value in pairs(self.table) do
if not callback(key, value) then
return false
end
end
return true
end
--- Returns all items in the collection except for those with specified keys
function Collection:except(keys)
local exceptList = {}
for key, value in pairs(keys) do
exceptList[value] = true
end
local tbl = self:new()
for key, value in pairs(self.table) do
if not exceptList[key] then
tbl:set(key, value)
end
end
return tbl
end
--- Internal function used to determine if a value is falsey
function Collection:falsyValue(value)
for k, v in ipairs({0, false, ''}) do
if v == value then
return true
end
end
if type(value) == 'table' then
if next(value) == nil then
return true
end
end
if not value then
return true
end
return false
end
--- Filters the collection using the given callback, keeping only items that pass a truth test
function Collection:filter(callback)
local filtered = self:new()
for key, value in pairs(self.table) do
local response = false
if callback then
response = callback(key, value)
elseif not self:falsyValue(value) then
response = true
end
if response then
filtered:set(key, value)
end
end
return filtered
end
--- Returns the first element in the collection, or that passes a truth test
function Collection:first(callback)
for key, value in pairs(self.table) do
if callback then
if callback(key, value) then
return value
end
else
return value
end
end
end
--- Flattens a multi-dimensional collection into a single dimension
function Collection:flatten(depth, tbl, currentDepth)
local flattened = self:new()
local iterable = tbl or self.table
currentDepth = currentDepth or 0
for key, value in pairs(iterable) do
if type(value) == 'table'
and ((depth and currentDepth < depth - 1) or not depth) then
flatInside = self:flatten(depth, value, currentDepth + 1):all()
for k, v in pairs(flatInside) do
flattened:push(v)
end
else
flattened:push(value)
end
end
if tbl then
return flattened
else
return flattened
end
end
--- Swaps the collection's keys with their corresponding values
function Collection:flip()
local flipped = self:new()
for key, value in pairs(self.table) do
flipped:set(value, key)
end
return flipped
end
--- Removes an item from the collection by its key
function Collection:forget(key)
if self.table[key] then
self.table[key] = nil
end
return self
end
--- Returns a collection containing the items that would be present for a given page number
function Collection:forPage(pageNumber, perPage)
local page = self:new()
local i = 1
for key, value in pairs(self.table) do
if i > (pageNumber - 1) * perPage and i <= pageNumber * perPage then
page:push(value)
end
i = i + 1
end
return page
end
--- Returns the itiem of a given key
function Collection:get(key, default)
if self.table[key] then
return self.table[key]
elseif default then
if type(default) == 'function' then
return default(key)
else
return default
end
end
end
--- Groups the collection's items by a given key
function Collection:groupBy(groupKey)
local grouped = self:new()
for key, value in pairs(self.table) do
local currentGroupKey = groupKey
if value[currentGroupKey] then
if not grouped:has(value[currentGroupKey]) then
grouped:set(value[currentGroupKey], {})
end
table.insert(grouped.table[value[currentGroupKey]], value)
end
end
return grouped
end
--- Determines if a given key exists in the collection
function Collection:has(key)
if self.table[key] then
return true
end
return false
end
--- Joins the items in a collection into a string
function Collection:implode(implodedKey, delimeter)
if type(self:first()) == 'table' then
local toImplode = {}
for key, value in pairs(self.table) do
if value[implodedKey] then
table.insert(toImplode, value[implodedKey])
end
end
return table.concat(toImplode, delimeter or ', ')
else
return table.concat(self.table, implodedKey or ', ')
end
end
--- Inserts a value at a given numeric index
function Collection:insert(value, position)
table.insert(self.table, position, value)
return self
end
--- Removes any values from the original collection that are not present in the passed table
function Collection:intersect(intersection)
local intersected = self:new()
local intersection = collect(intersection):flip():all()
for key, value in pairs(self.table) do
if intersection[value] then
intersected:set(key, value)
end
end
return intersected
end
--- Determines whether the collection is associative
function Collection:isAssociative()
if self:count() > #self.table then
return true
end
return false
end
--- Determines if the collection is empty
function Collection:isEmpty()
if next(self.table) == nil then
return true
end
return false
end
--- Determines if the collection is not empty
function Collection:isNotEmpty()
if next(self.table) ~= nil then
return true
end
return false
end
--- Keys the collection by the given key
function Collection:keyBy(keyName)
local keyed = self:new()
for key, value in pairs(self.table) do
if type(keyName) == 'function' then
local response = keyName(key, value)
keyed:set(response, value)
else
keyed:set(value[keyName], value)
end
end
return keyed
end
--- Returns a list of the collection's keys
function Collection:keys()
local keys = self:new()
for key, value in pairs(self.table) do
keys:push(key)
end
return keys
end
--- Returns the last element in the collection, or that passes a truth test
function Collection:last(callback)
local currentValue
for key, value in pairs(self.table) do
if callback then
if callback(key, value) then
currentValue = value
end
else
currentValue = value
end
end
return currentValue
end
--- Iterates through the collection and passes each value to the callback, which can then modify the values
function Collection:map(callback)
local remapped = self:new()
for key, value in pairs(self.table) do
newKey, newValue = callback(key, value)
remapped:set(newKey, newValue)
end
return remapped
end
--- Iterates through the the collection and remaps the key and value based on the return of a callback
function Collection:mapWithKeys(callback)
local mapped = self:new()
for key, value in pairs(self.table) do
local k, v = callback(key, value)
mapped:set(k, v)
end
return mapped
end
--- Returns the maximum value of a set of given values
function Collection:max(maxKey)
local max
for key, value in pairs(self.table) do
if maxKey then
if not max or value[maxKey] > max then
max = value[maxKey]
end
else
if not max or value > max then
max = value
end
end
end
return max
end
--- Returns the median value of a set of given values
function Collection:median(medianKey)
local all = {}
for key, value in pairs(self.table) do
if medianKey then
table.insert(all, value[medianKey])
else
table.insert(all, value)
end
end
table.sort(all, function(a, b) return a < b end)
if math.fmod(#all, 2) == 0 then
return (all[#all / 2] + all[(#all / 2) + 1] ) / 2
else
return all[math.ceil(#all/2)]
end
end
--- Merges the given table with the original collection
function Collection:merge(toMerge)
local merged = self:clone()
for key, value in pairs(toMerge) do
if type(key) == 'number' then
merged:push(value)
else
merged:set(key, value)
end
end
return merged
end
--- Returns the minimum value of a set of given values
function Collection:min(minKey)
local min
for key, value in pairs(self.table) do
if minKey then
if not min or value[minKey] < min then
min = value[minKey]
end
else
if not min or value < min then
min = value
end
end
end
return min
end
--- Returns the mode value of a given key
function Collection:mode(modeKey)
local counts = {}
for key, value in pairs(self.table) do
if modeKey then
value = value[modeKey]
end
if counts[value] == nil then
counts[value] = 1
else
counts[value] = counts[value] + 1
end
end
local biggestCount = 0
for key, value in pairs(counts) do
if value > biggestCount then
biggestCount = value
end
end
local temp = self:new()
for key, value in pairs(counts) do
if value == biggestCount then
temp:push(key)
end
end
return temp
end
--- Creates a new collection instance
function Collection:new(tbl)
return setmetatable({ table = tbl or {} }, { __index = self, __tostring = self.toString })
end
--- Creates a new collection consisting of every nth element
function Collection:nth(step, offset)
local nth = self:new()
local position = 1
offset = (offset and offset + 1) or 1
for key, value in pairs(self.table) do
if position % step == offset then
nth:push(value)
end
position = position + 1
end
return nth
end
--- Returns the items in the collection with the specified keys
function Collection:only(keys)
local onlyList = {}
for key, value in pairs(keys) do
onlyList[value] = true
end
local tbl = self:new()
for key, value in pairs(self.table) do
if onlyList[key] then
tbl:set(key, value)
end
end
return tbl
end
--- Returns a pair of elements that pass and fail a given truth test
function Collection:partition(callback)
local valid = Collection:new()
local invalid = Collection:new()
for key, value in pairs(self.table) do
local result = callback(key, value)
if result then
valid:push(value)
else
invalid:push(value)
end
end
return valid, invalid
end
-- Passes the collection to the given callback and returns the result
function Collection:pipe(callback)
return callback(self)
end
--- Retrives all of the values for a given key
function Collection:pluck(valueName, keyName)
local plucked = self:new()
for key, value in pairs(self.table) do
if value[valueName] then
if keyName then
plucked:set(value[keyName], value[valueName])
else
plucked:push(value[valueName])
end
end
end
return plucked
end
--- Removes and returns the last item from the collection
function Collection:pop()
return table.remove(self.table, #self.table)
end
--- Adds an item to the beginning of the collection
function Collection:prepend(value)
table.insert(self.table, 1, value)
return self
end
--- Removes and returns an item from the collection by key
function Collection:pull(key)
if type(key) == 'number' then
return table.remove(self.table, key)
else
local pulled = self.table[key]
self.table[key] = nil
return pulled
end
end
--- Sets the given key and value in the collection
function Collection:put(key, value)
self.table[key] = value
return self
end
--- Returns a random item or number of items from the collection
function Collection:random(count, rep)
local all = self:new(self.table):convertToIndexed():all()
local random = self:new()
count = count or 1
if count < 0 then
error('Positive number expected, negative number given.')
end
for i = 1, count do
if #all > 0 then
local randomElement
if rep then
randomElement = all[math.random(#all)]
else
randomElement = table.remove(all, math.random(#all))
end
random:push(randomElement)
end
end
if count == 1 and random[1] then
return random[1]
end
return random
end
--- Reduces the collection to a single value, passing the result of each iteration into the next
function Collection:reduce(callback, default)
local carry = default
for key, value in pairs(self.table) do
carry = callback(carry, value)
end
return carry
end
--- Filters the collection using the given fallback
function Collection:reject(callback)
local notRejected = self:new()
for key, value in pairs(self.table) do
local rejected = false
if callback then
rejected = callback(key, value)
elseif not self:falsyValue(value) then
rejected = true
end
if not rejected then
notRejected:set(key, value)
end
end
return notRejected
end
--- Fixes numerical keys to put them in order
function Collection:resort()
local sorted = self:new()
for key, value in pairs(self.table) do
if type(key) == 'number' then
sorted:push(value)
else
sorted[key] = value
end
end
return sorted
end
--- Reverses the order of the numerical keys in the collection
function Collection:reverse()
local reversed = self:new()
for key, value in pairs(self.table) do
if type(key) == 'number' then
reversed:prepend(value)
else
reversed:set(key, value)
end
end
return reversed
end
--- Searches the collection for a value and returns the key
function Collection:search(callback)
for key, value in pairs(self.table) do
if type(callback) == 'function' then
result = callback(key, value)
if result then
return key
end
else
if callback == value then
return key
end
end
end
end
--- Removes and returns the first item from the collection
function Collection:shift()
for key, value in pairs(self.table) do
if type(key) == 'number' then
return table.remove(self.table, key)
else
local response = value
self.table[key] = nil
return value
end
end
end
--- Randomly shuffles the order of items in the collection
function Collection:shuffle()
local shuffled = self:new()
local numericKeys = {}
local numericValues = {}
for key, value in pairs(self.table) do
if type(key) == 'number' then
table.insert(numericKeys, key)
table.insert(numericValues, value)
end
end
for key, value in pairs(self.table) do
if type(key) == 'number' then
shuffled:set(table.remove(numericKeys, math.random(#numericKeys)), table.remove(numericValues, math.random(#numericValues)))
-- todo: make this push into a random spot in the array
else
shuffled:set(key, value)
end
end
return shuffled
end
--- Returns a slice of the collection at the given index
function Collection:slice(index, size)
local slice = self:new()
local i = 0
for key, value in ipairs(self.table) do
if key >= index + 1 then
slice:append(value)
if size then
i = i + 1
if i == size then
return slice
end
end
end
end
return slice
end
--- Sorts the items in the collection
function Collection:sort(callback)
local sorted = self:clone()
if callback and type(callback) == 'function' then
table.sort(sorted.table, callback)
elseif callback then
table.sort(sorted.table, function(a, b) return a[callback] < b[callback] end)
else
table.sort(sorted.table, function(a, b) return a < b end)
end
return sorted
end
--- Same as the Collection:sort() method, but returns the collection in the opposite order
function Collection:sortDesc(callback)
local sorted = self:clone()
if callback and type(callback) == 'function' then
table.sort(sorted.table, callback)
sorted = sorted:reverse()
elseif callback then
table.sort(sorted.table, function(a, b) return a[callback] > b[callback] end)
else
table.sort(sorted.table, function(a, b) return a > b end)
end
return sorted
end
--- Removes and returns a slice of items starting at the specified index
function Collection:splice(index, size, replacements)
local spliced = self:new()
local toRemove = {}
local i = 0
for key, value in ipairs(self.table) do
if key >= index + 1 then
spliced:append(value)
table.insert(toRemove, key)
if size then
i = i + 1
if i == size then
break
end
end
end
end
local removedIndex = 0
for _, key in pairs(toRemove) do
if type(key) == 'number' then
table.remove(self.table, key + removedIndex)
removedIndex = removedIndex - 1
else
self.table[key] = nil
end
end
if replacements then
for i = 1, #replacements do
self:insert(table.remove(replacements, #replacements), index + 1)
end
end
return spliced
end
--- Breaks the collection into the given number of groups
function Collection:split(count)
local groupSize = math.ceil(self:count() / count)
return self:chunk(groupSize)
end
--- Returns the sum of items in the collection
function Collection:sum(key)
local sum = 0
for i, value in pairs(self.table) do
if key then
if value[key] then
sum = sum + value[key]
else
error('Value "' .. key .. '" does not exist in collection object with key "' .. i .. '"')
end
else
sum = sum + value
end
end
return sum
end
--- Returns a collection with the specified number of items
function Collection:take(count)
taken = self:new()
if count >= 0 then
for i = 1, count do
if self.table[i] then
taken:append(self.table[i])
else
break
end
end
else
local iterations = 0
for i = #self.table, 1, -1 do
if self.table[i] then
taken:prepend(self.table[i])
else